glyphs

ABU 16-Sep-2024 - Glyphs 1.5 - adapted for Tcl9 compatibility - new features and bug fix.

Glyphs is a pure-tcl library for digging into TrueType font-files.

Glyphs is able to extract the vectorial paths of each glyph - points, lines, curves.

See also glyphs-demo

Image glyphBeta

Download

  • glyphs 1.5 [L1 ] (16-Sep-2024)
    • Tcl9 compatible.
    • BUGFIX - Severe error for composite glyphs having negative scale (eg. glyph 599 of Arial.ttf)
    • Added reference manual
    • Removed deprecated Glyphs methods:
      • nameinfo --use--> "name.info"
      • glyphByCharCode --use--> "glyphByNumCode"
    • Glyphs method "get": added a new property "name"
    • Glyph method "get": added a new property "name"
    • Removed deprecated Glyph features:
      • $glyph get paths --use--> $glyph get SVG
      • $glyph get pathLengths --use--> $glyph get contoursLength

  Change History
  • glyphs 1.4.1 [L2 ] (13-Aug-2024)
    • adapted for Tcl9 compatibility. Redesigned replacing Itcl with TclOO . Some API additions from 1.3 and 1.4 (read the include 'glyphs.txt').
  • glyphs 1.2.7 [L3 ] (28-Sep-2022)
    • No API changes. Use an updated and more accurate logic for selecting detailed info from the NAME table.
  • glyphs 1.2.6 [L4 ] (9-Feb-2020)
    • No API changes. Use an updated and more accurate math library for Bezier curves.
  • glyphs 1.2 [L5 ] (2-Jul-2018)
    • Added support for OpenTypeCollections (*.ttc)
    • Added support for CFF font-files (Postscript outlines) (*.otf)
    • nameinfo method is now deprecated ; use the new name.info method
      • WARNING: nameinfo returns a triplet {id idstring value} ;
      • name.info returns just the value.
  • glyphs 1.1.2 [L6 ] (9-May-2018)
    • bug-fix: on error during the initial parsing of a tff, file-handle is not closed (FIXED)
    • fixed some properties names (Ascender Descender LineGap ...); they become 'camelCase' (ascender descender lineGap ...) . Old names are still supported although deprecated
  • glyphs 1.1.1 [L7 ]
    • bug-fix: wrong SVG countours for glyphs having no control-points on-the-curve (FIXED)
    • bug-fix: wrong glyph selection for "notdef" glyphes or empty glyphes (like "space") (FIXED)
  • glyphs 1.1 [L8 ]
    • added methods for getting glyph's advancewidth, leftsidebearing, chars
    • added methods for kerning
    • updated test-suite
  • glyphs 1.0.1 [L9 ]
    • FIX for TclTk 8.6.1 BUG (Itcl incompatibility). Changes now works with TclTk 8.6.1 as well with previous versions.
  • glyphs 1.0 [L10 ] (BUG)
  • glyphs 0.9 [L11 ] (OLD)

  Glyphs 1.5 - Reference Manual

Glyphs 1.5 Glyphs glyphs extraction

Inspecting and extracting glyphs from font-files

SYNOPSIS

package require Tcl 8.6

package require Glyphs ?1.5?

  • Glyphs::new font-file ?idx?
  • fontObj destroy
  • fontObj name.info
  • fontObj name.info property
  • fontObj get
  • fontObj get property
  • fontObj numcode2glyphIndex numcode
  • fontObj unicode2glyphIndex unicode
  • fontObj gget idx
  • fontObj gget idx property
  • fontObj getKerning idx1 idx2 ?orientation?
  • fontObj glyph idx
  • fontObj glyphByNumcode numcode
  • fontObj glyphByUnicode unicode
  • glyphObj destroy
  • glyphObj get
  • glyphObj get property ?options?
  • glyphObj onUniformDistance dL meth ?Mtx?
  • glyphObj onUniformSteps nOfSteps meth ?Mtx?

DESCRIPTION

Package Glyphs is a pure-tcl library for digging into TrueType/OpenType font-files. Glyphs is able to extract most font metadata as well the vectorial paths of each glyph - points, lines, curves. The Glyphs package is based on 2 main classes: Glyphs, whose instances correspond to a font-file such as a *.ttf or a *.otf or a *.ttc, and class Glyph, whose instances expose more detailed methods and properties of the single glyphs.

Glyphs

In order to inspect and extract glyphs and metadata from a font-file, you should open it with this command:

Glyphs::new font-file ?idx?
This is the main command: it opens the font-file and returns a fontObj to be used in subsequent operations.

If font-file is an OpenType Collection (*.ttc), containing more than one font, you can specify idx for selecting a particolar font. If you dont' specify any, the first font is selected.

Glyphs methods

fontObj destroy
destroys the fontObj and all the related resources (i.e. all the subsequently opened glyphObjs).
fontObj name.info
return the tags of the font's general properties (extracted from the 'name' table), both as a numerical-index and as a symbolic-name:
  • 0 copyright
  • 1 fontFamily
  • 2 fontSubfamily
  • 3 uniqueID
  • 4 fullName
  • 5 version
  • 6 postScriptName
  • 7 trademark
  • 8 manufacturer
  • 9 designer
  • 10 description
  • 11 manufacturerURL
  • 12 designerURL
  • 13 license
  • 14 licenseURL
fontObj name.info property
Given a valid property as a numerical-index or as a symbolic-name, return the value of that property, or the empty string if property is not valid.
    package require Glyphs
    set fontObj [Glyphs::new /mydir/Arial.ttf]
    $fontObj get fontFamily  ;#  -->  Monotype:Arial Regular:Version 3.00 (Microsoft)
fontObj get
returns a list with all the valid font's properties names
fontObj get property
Given a valid property, return the value of that property, or the empty string if property is not valid. Valid properties are:
  • advanceWidthMax
  • ascender
  • bbox - as a list of xMin,yMin,xMax,yMax for all the glyphs' bboxes.
  • caretOffset
  • caretSlopeRise
  • caretSlopeRun
  • descender
  • fontPath - the full pathname of the loaded font-file.
  • fontRevision
  • lineGap
  • metricDataFormat
  • minLeftSideBearing
  • minRightSideBearing
  • numGlyphs - number of glyphs in this font.
  • numberOfHMetrics
  • subFonts - list of fonts contained in the font-file.
  • unitsPerEm
  • xMaxExtent Usually glyphs can be extracted by specifying their index, but as a convenience, the major part of font-files holds a table for mapping a "character" (unicode) to index.

For your convenience, you can determine the glyph-index for a given unicode character, with these two methods:

fontObj numcode2glyphIndex numcode
given the numerical-code of an unicode char, return its glyph index, or 0 if there's no glyph for that numcode.
fontObj unicode2glyphIndex unicode
given an unicode character, expressed as a single charater ("A"), or with the \u notation (\u0041), return its glyph index, or 0 if there's no glyph for that unicode.
fontObj gget idx
return the tags of a specific glyph-index. Valid tags are:
  • advanceWidth
  • chars
  • leftSideBearing
  • name
fontObj gget idx property
return the value of property for the glyph at idx, or the empty string if idx or property are not valid.
fontObj getKerning idx1 idx2 ?orientation?
return the space-adjustment between two glyphs. orientation is "H" (default) or "V"
  # fontObj is for "Arial.ttf"
  #  get the kerning between "A" and "V"
  $fontObj getKerning [$fontObj unicode2glyphIndex "A"] [$fontObj unicode2glyphIndex "V"] ;# --> -152

NOTE: Limitations

  • only kerning-tables "format 0" are supported.
  • tables with "minimum" values (instead of kerning values) are not suported.
  • tables with cross-stream (perpendicular) kerning are not supported
fontObj glyph idx
return a glyphObj to be used in subsequent operations or raises an error if idx is out of range. See Glyph methods. Note that if the requested glyph has been previously requested (and has not been destroyed), this method reuses the handle of the previously requested glyph.
fontObj glyphByNumcode numcode
similar to the glyph method; glyph is specified by its numcode instead of its index.
fontObj glyphByUnicode unicode
similar to the glyph method; glyph is specified by its unicode instead of its index.

Glyph

As stated before, a Glyph object is extracted from a fontObj with the following command:

  set glyphObj [$fontObj glyph $idx]

then, the following methods can be used:

Glyph methods

glyphObj destroy
destroys the glyphObj and release all the related resources. Note that when a fontObj is destroyed, all its related glyphObjs are automatically destroyed, too.
glyphObj get
returns a list with all the valid glyph's properties.
glyphObj get property ?options?
Valid property, as returned by the above method are:
  • SVG - the glyph geometry expressed as lists of SVG commands (grouped by contours).

It's possible to specify a trailing optional reduced affine matrix that can be used for translating, rotatating, scaling the resulting geometry. See Affine Transformations

  • advanceWidth
  • bbox
  • chars - unicode character
  • contours - the glyph geometry expressed as a list of BContours objects.

A BContour object is a sequence of connected Bezier curves Note that the caller has the ownership of these objects and it's her responsability to deallocate them. (see the documentation of theincluded Bezier package)

  • contoursLength - a list with the lengths of each contour.
  • index - the physical index of this glyph in its font-file
  • instructions - .. binary stuff ..
  • leftSideBearing
  • points - a list of lists (one for each contour).

Each contour is made of a sequence of triples x y flag. Flag "1" means that point (x,y) is on-curve, Flag "0" means point (x,y) is the control point of a Quadratic Bezier curve. Glyphs can be segmented, i.e. splitted in a sequence of points placed on their contour. There are two methods for doing a segmentation of a glyph

  • onUniformDistance
  • onUniformSteps These methods can return a sequence of points (as a list of two floating point numbers), grouped by contour, as well tangents and normals.

Depending on the specified meth, they return a sequence of 2D points, grouped by contour. Supported meth are

  • at - return a sequence of {x y} points
  • tangent_at - return a sequence of tangent *versors*
  • normal_at - return a sequence of normal *versors*
  • vtangent_at - return a sequence {x y} {dx dy} (first point is the 'at' point, second point is its tangent versor)
  • vnormal_at - return a sequence {x y} {dx dy} (first point is the 'at' point, second point is its normal versor) Moreover these two methods support a trailing optional parameter Mtx for the specification of an affine matrix (see Affine Transformations)
glyphObj onUniformDistance dL meth ?Mtx?
this method splits the whole glyph in a sequence of {x y} points equally spaced at a distance dL.
$glyphObj onUniformDistance 100.0 "at"

returns N (long) lists (one list for each glyph's countour) made of {x y} points; each {x y} is a point on the glyph, and all these points are equally spaced along the curves (with some arrangements for dealing with the curves extremities ...)

$glyphObj onUniformDistance 100.0 "vtangent_at"

similar to to previous command, but instead of a sequence of points {x y}, it returns a sequence of {x y} {dx dy} where {dx dy} is the tangent versor at {x y}

glyphObj onUniformSteps nOfSteps meth ?Mtx?
the result is similar to that of the previous method, except that the points on the contour are calculated by evaluating N points on each elementary Bezier-curve (whose parametric equation is B(t)) by varying t in steps of 1/N
$glyphObj onUniformSteps 5 "at"

returns N (long) lists (one list for each glyph's countour) made of {x y} points; each curve of a contour is splitted in 5 parts, coresponding to B(0),B(1/5),B(2/5)..B(4/5). ( B(t) is the parametric equation of each curve )

Drawing a glyph

It's your app's responsability to translate the contours (or the SVG commands) in real drawing commands. A simple implementation for drawing the "SVG" in a standard tk-canvas widget could be:

proc SVG2Canvas { contours cvs } {
    foreach contour $contours {
         # first command should be M (MOVETO)
        foreach pCmd $contour {
            set points [lassign $pCmd cmd]
            switch -- $cmd {
                M { ;# MOVETO
                    ;
                }
                L { ;# LINETO
                    $cvs create line $lastX $lastY {*}$points
                }
                Q { ;# QUADTO
                    $cvs create line $lastX $lastY {*}$points -smooth true
                }
                C { ;# CUBICTO
                    $cvs create line $lastX $lastY {*}$points -smooth raw
                }
                default { error "unrecognized SVG command \"$cmd\"" }
            }
            set lastX [lindex $points end-1]
            set lastY [lindex $points end]
        }
    }
}

SVG2Canvas [$glyphObj get SVG] $cvs

For more complete examples, including how to draw all the subdivision points on the glyphs contours, see the companion demo-app at https://wiki.tcl-lang.org/page/glyphs-demo

Affine Transformations

An affine transformation is used for translating/scaling/rotating a set of points (such as those returned by the onUnifomDistance/onUniformSteps methods) as well as the curves provided by the SVG command For Glyphs (in a 2D space) a reduced affine matrix is a shortand of a standard 3x3 affine matrix, but expressed as a list of the columns' coefficients

 Standard affine matrix          reduced affine matrix
  |a b 0|
  |c d 0|               --->  { {a c e} {b d f} }
  |e f 1|

KEYWORDS

font

COPYRIGHT

 Copyright (c) 2024, by A.Buratti

 Quick User Guide

Opening and closing

This is a first quick trip; just open a .ttf, look inside and close

> package require Glyphs
> set fObj [Glyphs::new "arial.ttf"]
> set ng [$fObj get numGlyphs]
> puts "found $ng glyphs"
> $fObj destroy

Inspecting font file's properties

Reopen the .ttf file

> set fObj [Glyphs::new "arial.ttf"]

we already know how to get the number-of-glyphs

> set res1 [$fObj get numGlyphs]
1674

and we can also get the overall-bounding box, or where the descender-line is placed

> set res2 [$fObj get bbox]
-1361 -665 4096 2060
>set res3 [$fObj get descender]
-434

but there're a lot of properties and the better way to know which properties are set is:

> set props [$fObj get]
fontPath numGlyphs bbox unitsPerEm fontRevision ascender descender .....

Other font properties can be retrieved from the internal 'name' table:

> foreach {id name} [$fObj name.info] {
>    lassign [$fObj name.info $id] id name value
>    puts "$id - $name\n$value\n"
> }
1 - Font Family name
Arial

2 - Font Subfamily name
Regular

3 - Unique font identifier
Monotype:Arial Regular:Version 3.00 (Microsoft)

4 - Full font name
Arial
 ....

Working with OpenType Collections

An OpenTypeCollection (*.ttc) contains more than one font. If you dont' specify any, the first font is selected

> set mfObj [Glyphs::new "AmericanTypewriter.ttc"]
> set ng [$mfObj get numGlyphs]
> puts "found $ng glyphs"

You can also get the list of included fonts

> foreach subFont [$mfObj get subFonts] {
>    puts $subFont"
> }

If you want to work with a specific subFont, then specify its index ( 0..n ) when opening the fontfile

> $mfObj close
> set mfObj [Glyphs::new "AmericanTypewriter.ttc" 3]
>  ...

Locating a single glyph

The quickest way to locate a single glyph is through its glyph-index. We know that "arial.ttf" has 1674 glyphs, therefore we can get all glyphs from 0 to 1674.

Now let's take the 144th glyph

>set g144 [$fObj glyph 144]

As with fObj's properties, we can get some specific glyph's property:

>$g144 get index 
144
>$g144 get bbox
1 0 1936 1466

In order to list all the available properties

> $g144 get
index bbox points instructions contoursLengths ...

Forget about the "instructions" property it's a binary string that "Glyphs" is not currently able to decode. (Probably it will removed in the next revision.)

The most important properties are points m contours and SVG

Let's take a glyph simpler than 144

>set g103 [$fObj glyph 103]
>set L [$g103 get points]
{99 714 1 99 1079 0 ...} {299 711 1 299 446 0 ...} {516 1556 1 516 1761 1 ...} {889 1556 1 ...}

Result looks like a list of 4 lists (4 contours).

Each contour is made of a sequence of triples: x y flag.

Flag "1" means that point (x,y) is on-curve, Flag "0" means point (x,y) is the control point of a Quadratic Bezier curve.

But, how to translate these "points" in a parametric curve?

>set L [$g103 get SVG]
{{M 99 714} {Q 99 1079 295.0 1285.5} ...} 
{{M 299 711} {Q 299 446 441.5 293.5} ... }
{{M 516 1556} {L 516 1761} ... }
{{M 889 1556} {L 889 1761} ...}

Result is a list of (4) contours.

Each contour is made of a sequence of simple commands:

  • M x y -- set (x,y) as the current point
  • L x y -- draw a line from current point to (x,y). (x,y) then becomes the current point
  • Q x1 y1 x2 y2 -- draw a quadratic bezier from current point, to (x1,y1) (control point) and to (x2,y2) (end-point). (x2,y2) then becomes the current point.
  • C x1 y1 x2 y2 x3 y3 -- draw a cubic bezier from current point, to (x1,y1) (control point), (x2,y2) (control point) and to (x3,y3) (end-point). (x3,y3) then becomes the current point

It's your app's responsability to translate these 'abstract commands' in real drawing commands. A very simple implementation for the standard canvas-widget could be the following:

proc SVG2Canvas { SVG cvs } {
    foreach contour $SVG {
         # first command should be M (MOVETO)
        foreach pCmd $contour {
            set points [lassign $pCmd cmd]
            switch -- $cmd {
                M {
                    ;
                }
                L {
                    $cvs create line $lastX $lastY {*}$points
                }
                Q {
                    $cvs create line $lastX $lastY {*}$points -smooth true
                }
                C { ;# CUBICTO
                    $cvs create line $lastX $lastY {*}$points -smooth raw
                }
                default { error "unrecognized SVG command \"$cmd\"" }            
            }
            set lastX [lindex $points end-1]
            set lastY [lindex $points end]            
        }
    }
}

SVG2Canvas [$g103 get SVG] .cvs

Look at glyphs-demo code for a more complete example.

Converting a glyph in a polyline, tangents, normals and more ..

The "onUniformDistance" method is a powerful method acting on a glyph.

We can 'split' the whole glyphs in a series of 2D points equally spaced.

> $g103 onUniformDistance 100.0 "at"

returns N (long) lists (one list for each glyph's countour) made of {x y} points; each point {x y} is on the glyph contour, and all these points are equally spaced (with some arrangements for dealing with the curves extremities ...)

We can also get the tangent or normal *versors* for these points.

> $g103 onUniformDistance 100.0 "tangent_at"
> $g103 onUniformDistance 100.0 "normal_at"

and finally we can get the tangent or normal *vectors* (i.e., a segment starting from point "at" having the tangent/normal direction).

> $g103 onUniformDistance 100.0 "vtangent_at"
> $g103 onUniformDistance 100.0 "vnormal_at"

We suggest to do some experiment with the glyphs-demo app.

Getting more glyph metrics

Getting the AdvanceWidth, LeftSideBearing, chars (list of all characters (unicode) sharing a glyph)

> $g103 get advanceWidth
1593
> $g103 get leftSideBearing
99
> $g103 get chars
214

The above commands are equivalent to:

> $fObj gget 103 advanceWidth
> $fObj gget 103 leftSideBearing
> $fObj gget 103 chars

Getting the horizontal kerning value for the "A" "V" letters

> $fObj getKerning [$fObj unicode2glyphIndex "A"] [$fObj unicode2glyphIndex "V"] "H"
-152

There's not much more to do with a single-glyph. Since it's a dynamic object, you can free space when it's no more useful,

>$g103 destroy

but, you are not required to do it. In fact, when the 'font-file' is destroyed

>$fObj destroy 

all space used for its allocated glyphs is freed.

More on accessing glyphs

We've seen the standard way to access a glyph by-index. The major part of TrueType Fonts holds a table for converting a "character" (unicode) to index.

>$fObj unicode2glyphIndex "A"
>$fObj unicode2glyphIndex "ß"    ; # unicode char \u03B2 (greek letter "Beta")
>$fObj unicode2glyphIndex \u03B2 ; # unicode char \u03B2 (greek letter "Beta")
>$fObj numcode2glyphIndex 946    ; # it's always the greek letter "Beta" !

For your convenience, you can access a glyph, with two new methods

>$fObj glyphByUnicode  "ß"  ; # glyph by Unicode
>$fObj glyphByCharCode 946  ; # glyph by CharCode