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
Download
Glyphs 1.5 Glyphs glyphs extraction
Inspecting and extracting glyphs from font-files
package require Tcl 8.6
package require Glyphs ?1.5?
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.
In order to inspect and extract glyphs and metadata from a font-file, you should open it with this command:
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.
package require Glyphs set fontObj [Glyphs::new /mydir/Arial.ttf] $fontObj get fontFamily ;# --> Monotype:Arial Regular:Version 3.00 (Microsoft)
For your convenience, you can determine the glyph-index for a given unicode character, with these two methods:
# fontObj is for "Arial.ttf" # get the kerning between "A" and "V" $fontObj getKerning [$fontObj unicode2glyphIndex "A"] [$fontObj unicode2glyphIndex "V"] ;# --> -152
NOTE: Limitations
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:
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
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)
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
Depending on the specified meth, they return a sequence of 2D points, grouped by contour. Supported meth are
$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 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 )
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
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|
font
Copyright (c) 2024, by A.Buratti
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
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 ....
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] > ...
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:
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.
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 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.
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