The Design of FreeType2
Copyright 1998–2000 David Turner (david@freetype.org)
Copyright 2000 The FreeType Development Team (devel@freetype.org)
- Introduction
- I. Components and APIs
- II. Public Objects and Classes
- III. Internal Objects and Classes
- IV. Module Classes
- To be continued…
Introduction
This document provides details on the design and implementation of the FreeType 2 library. Its goal is to allow developers to better understand the way how FreeType 2 is organized, in order to let them extend, customize, and debug it.
Before anything else, it is important to understand the purpose of this library, i.e., why it has been written:
-
It allows client applications to access font files easily, wherever they could be stored, and as independently of the font format as possible.
-
Easy retrieval of global font data most commonly found in normal font formats (i.e. global metrics, encoding/charmaps, etc.).
-
It allows easy retrieval of individual glyph data (metrics, images, name, anything else).
-
Access to font format-specific “features” whenever possible (e.g. SFNT tables, Multiple Masters, OpenType Layout tables, etc.).
Its design has also severely been influenced by the following requirements:
-
High portability: The library must be able to run on any kind of environment. This requirement introduces a few drastic choices that are part of FreeType 2’s low-level system interface.
-
Extendability: New features should be added with the least modifications in the library’s code base. This requirement induces an extremely simple design where nearly all operations are provided by modules.
-
Customization: It should be easy to build a version of the library that only contains the features needed by a specific project. This really is important when you need to integrate it in a font server for embedded graphics libraries.
-
Compactness and efficiency: The primary target for this library are embedded systems with low cpu and memory resources.
The rest of this document is divided in several sections. First, a few chapters will present the library’s basic design as well as the objects/data managed internally by FreeType 2.
A later section is then dedicated to library customization, relating such topics as system-specific interfaces, how to write your own module and how to tailor library initialization & compilation to your needs.
I. Components and APIs
It’s better to describe FreeType 2 as a collection of components. Each one of them is a more or less abstract part of the library that is in charge of one specific task. We will now explicit the connections and relationships between them.
A first brief description of this system of components could be:
-
Client applications typically call the FreeType 2 high-level API, whose functions are implemented in a single component called the Base Layer.
-
Depending on the context or the task, the base layer then calls one or more module components to perform the work. In most cases, the client application doesn’t need to know which module was called.
-
The base layer also contains a set of routines that are used for generic things like memory allocation, list processing, i/o stream parsing, fixed-point computation, etc. these functions can also be called by a module at any time, and they form what is called the low-level base API.
This is illustrated by the following graphics (note that component entry points are represented as colored triangles):
Now, a few additional things must be added to complete this picture:
-
Some parts of the base layer can be replaced for specific builds of the library, and can thus be considered as components themselves. This is the case for the
ftsystem
component, which is in charge of implementing memory management & input stream access, as well asftinit
, which is in charge of library initialization (i.e. implementing theFT_Init_FreeType()
function). -
FreeType 2 comes also with a set of optional components, which can be used either as a convenience for client applications (e.g. the
ftglyph
component, used to provide a simple API to manage glyph images independently of their internal representation), or to access format-specific features (e.g. theftmm
component used to access and manage Multiple Masters data in Type 1 fonts). -
Finally, a module is capable of calling functions provided by another module. This is very useful to share code and tables between several font driver modules (for example, the
truetype
andcff
modules both use the routines provided by thesfnt
module).
Hence, a more complete picture would be:
Please take note of the following important points:
-
An optional component can use either the high-level or base API. This is the case of
ftglyph
in the above picture. -
Some optional components can use module-specific interfaces ignored by the base layer. In the above example,
ftmm
directly accesses the Type 1 module to set/query data. -
A replaceable component can provide a function of the high-level API. For example,
ftinit
providesFT_Init_FreeType()
to client applications.
II. Public Objects and Classes
We will now explain the abstractions provided by FreeType 2 to client applications to manage font files and data. As you would normally expect, these are implemented through objects/classes.
1. Object Orientation in FreeType 2
Though written in ANSI C, the library employs a few techniques, inherited from object-oriented programming, to make it easy to extend. Hence, the following conventions apply in the FreeType 2 source code:
-
Each object type/class has a corresponding structure type and a corresponding structure pointer type. The latter is called the handle type for the type/class.
Consider that we need to manage objects of type “foo” in FreeType 2. We would define the following structure and handle types as follows:
typedef struct FT_FooRec_* FT_Foo; typedef struct FT_FooRec_ { // fields for the "foo" class ... } FT_FooRec;
As a convention, handle types use simple but meaningful identifiers beginning with
FT_
, as inFT_Foo
, while structures use the same name with aRec
suffix appended to it (“Rec” is short for “record”). Note that each class type has a corresponding handle type. -
Class derivation is achieved internally by wrapping base class structures into new ones. As an example, we define a “foobar” class that is derived from “foo”. We would do something like:
typedef struct FT_FooBarRec_* FT_FooBar; typedef struct FT_FooBarRec_ { // the base "foo" class fields FT_FooRec root; // fields proper to the "foobar" class ... } FT_FooBarRec;
As you can see, we ensure that a “foobar” object is also a “foo” object by placing a
FT_FooRec
at the start of theFT_FooBarRec
definition. It is called root by convention.Note that a
FT_FooBar
handle also points to a “foo” object and can be typecasted toFT_Foo
. Similarly, when the library returns aFT_Foo
handle to client applications, the object can be really implemented as aFT_FooBar
or any derived class from “foo”.
In the following sections of this chapter, we will refer to “the FT_Foo
class” to indicate the type of objects handled through FT_Foo
pointers, be
they implemented as “foo” or “foobar”.
2. The FT_Library
class
This type corresponds to a handle to a single instance of the library. Note
that the corresponding structure FT_LibraryRec
is not defined in public
header files, making client applications unable to access its internal fields.
The library object is the parent of all other objects in FreeType 2. You need to create a new library instance before doing anything else with the library. Similarly, destroying it will automatically destroy all its children (i.e. faces and modules).
Typical client applications should call FT_Init_FreeType()
in order to create
a new library object, ready to be used for further actions.
Another alternative is to create a fresh new library instance by calling the
function FT_New_Library()
, defined in the <freetype/ftmodule.h>
public
header file. This function will however return an “empty” library instance
with no module registered in it. You can “install” modules in the instance by
calling FT_Add_Module()
manually.
Calling FT_Init_FreeType()
is a lot more convenient, because this function
basically registers a set of default modules into each new library instance.
The way this list is accessed and/or computed is determined at build time, and
depends on the content of the ftinit
component. This process is explained in
details later in this document.
For now, one should consider that library objects are created with
FT_Init_FreeType()
, and destroyed along with all children with
FT_Done_FreeType()
.
3. The FT_Face
class
A face object corresponds to a single font face, i.e., a specific typeface with a specific style. For example, “Arial” and “Arial Italic” correspond to two distinct faces.
A face object is normally created through FT_New_Face()
. This function takes
the following parameters: an FT_Library
handle, a C file pathname used to
indicate which font file to open, an index used to decide which face to load
from the file (a single file may contain several faces in certain cases), and
the address of a FT_Face
handle. It returns an error code:
FT_Error FT_New_Face( FT_Library library,
const char* filepathname,
FT_Long face_index,
FT_Face* face );
In case of success, the function will return 0, and the handle pointed to by
the face
parameter will be set to a non-NULL value.
Note that the face object contains several fields used to describe global font
data that can be accessed directly by client applications. For example, the
total number of glyphs in the face, the face’s family name, style name, the EM
size for scalable formats, etc. For more details, look at the FT_FaceRec
definition in the FreeType 2 API Reference.
4. The FT_Size
class
Each FT_Face
object has one or more FT_Size
objects. A size object is
used to store data specific to a given character width and height. Each newly
created face object has one size, which is directly accessible as face->size
.
The contents of a size object can be changed by calling either
FT_Set_Pixel_Sizes()
or FT_Set_Char_Size()
.
A new size object can be created with FT_New_Size()
, and destroyed manually
with FT_Done_Size()
. Note that typical applications don’t need to do this
normally: they tend to use the default size object provided with each
FT_Face
.
The public fields of FT_Size
objects are defined in a very small structure
named FT_SizeRec
. However, it is important to understand that some font
drivers define their own derivatives of FT_Size
to store important internal
data that is re-computed each time the character size changes. Most of the
time, these are size-specific font hints.
For example, the TrueType driver stores the scaled CVT table that results from
the execution of the “cvt” program in a TT_Size
structure, while the Type 1
driver stores scaled global metrics (like blue zones) in a T1_Size
object.
Don’t worry if you don’t understand the current paragraph; most of this stuff
is highly font format specific and doesn’t need to be explained to client
developers :-)
5. The FT_GlyphSlot
class
The purpose of a glyph slot is to provide a place where glyph images can be loaded one by one easily, independently of the glyph image format (bitmap, vector outline, or anything else).
Ideally, once a glyph slot is created, any glyph image can be loaded into it without additional memory allocation. In practice, this is only possible with certain formats like TrueType which explicitly provide data to compute a slot’s maximum size.
Another reason for glyph slots is that they are also used to hold format-specific hints for a given glyphs as well as all other data necessary to correctly load the glyph.
The base FT_GlyphSlotRec
structure only presents glyph metrics and
images to client applications, while actual implementation may contain more
sophisticated data.
As an example, the TrueType-specific TT_GlyphSlotRec
structure
contains additional fields to hold glyph-specific bytecode, transient outlines
used during the hinting process, and a few other things.
The Type 1-specific T1_GlyphSlotRec
structure holds glyph hints during
glyph loading, as well as additional logic used to properly hint the glyphs
when a native Type 1 hinter is used.
Finally, each face object has a single glyph slot that is directly accessible
as face->glyph
.
6. The FT_CharMap
class
The FT_CharMap
type is used as a handle to character map objects, or
charmaps. A charmap is simply some sort of table or dictionary which is used
to translate character codes in a given encoding into glyph indices for the
font.
A single face may contain several charmaps. Each one of them corresponds to a given character repertoire, like Unicode, Apple Roman, Windows codepages, and other encodings.
Each FT_CharMap
object contains a “platform” and an “encoding” field
used to identify precisely the character repertoire corresponding to it.
Each font format provides its own derivative of FT_CharMapRec
and thus
needs to implement these objects.
7. Objects relationships
The following diagram summarizes what we have just said regarding the public objects managed by the library, as well as explicitly describes their relationships
Note that this picture will be updated at the end of the next chapter, related to internal objects.
III. Internal Objects and Classes
Let us have a look now at the internal objects that FreeType 2 uses, i.e., those not directly available to client applications, and see how they fit into the picture.
1. Memory management
All memory management operations are performed through three specific routines
of the base layer, namely: FT_Alloc()
, FT_Realloc()
, and FT_Free()
. Each
one of these functions expects a FT_Memory
handle as its first parameter.
The latter is a pointer to a simple object used to describe the current memory
pool/manager. It contains a simple table of alloc/realloc/free functions. A
memory manager is created at library initialization time by
FT_Init_FreeType()
, calling the function FT_New_Memory()
provided by the
ftsystem
component.
By default, this manager uses the ANSI malloc()
, realloc()
, and free()
functions. However, as ftsystem
is a replaceable part of the base layer, a
specific build of the library could provide a different default memory manager.
Even with a default build, client applications are still able to provide their
own memory manager by not calling FT_Init_FreeType()
but follow these simple
steps:
-
Create a new
FT_Memory
object by hand. The definition ofFT_MemoryRec
is located in the public file<freetype/ftsystem.h>
. -
Call
FT_New_Library()
to create a new library instance using your custom memory manager. This new library doesn’t yet contain any registered modules. -
Register the set of default modules by calling the function
FT_Add_Default_Modules()
provided by theftinit
component, or manually register your drivers by repeatedly callingFT_Add_Module()
.
2. Input streams
Font files are always read through FT_Stream
objects. The definition of
FT_StreamRec
is located in the public file <freetype/ftsystem.h>
, which
allows client developers to provide their own implementation of streams if they
wish so.
The function FT_New_Face()
will always automatically create a new stream
object from the C pathname given as its second argument. This is achieved by
calling the function FT_New_Stream()
provided by the ftsystem
component.
As the latter is replaceable, the implementation of streams may vary greatly
between platforms.
As an example, the default implementation of streams is located in the file
src/base/ftsystem.c
and uses the ANSI fopen()
, fseek()
, and fread()
calls. However, the Unix build of FreeType 2 provides an alternative
implementation that uses memory-mapped files, when available on the host
platform, resulting in a significant access speed-up.
FreeType distinguishes between memory-based and disk-based streams. In the first case, all data is directly accessed in memory (e.g. ROM-based, write-only static data and memory-mapped files), while in the second, portions of the font files are read in chunks called frames, and temporarily buffered similarly through typical seek/read operations.
The FreeType stream sub-system also implements extremely efficient algorithms to very quickly load structures from font files while ensuring complete safety in the case of a “broken file”.
The function FT_New_Memory_Face()
can be used to directly create/open a
FT_Face
object from data that is readily available in memory (including
ROM-based fonts).
Finally, in the case where a custom input stream is needed, client applications
can use the function FT_Open_Face()
, which can accept custom input streams.
This may be useful in the case of compressed or remote font files, or even
embedded font files that need to be extracted from certain documents.
Note that each face owns a single stream, which is also destroyed by
FT_Done_Face()
. Generally speaking, it is certainly not a good idea to
keep numerous FT_Face
objects opened.
3. Modules
A FreeType 2 module is itself a piece of code. However, the library creates a
single FT_Module
object for each module that is registered when
FT_Add_Module()
is called.
The definition of FT_ModuleRec
is not publicly available to client
applications. However, each module type is described by a simple public
structure named FT_Module_Class
, defined in <freetype/ftmodule.h>
, and is
described later in this document:
You need a pointer to an FT_Module_Class
structure when calling
FT_Add_Module()
, whose declaration is:
FT_Error FT_Add_Module( FT_Library library,
const FT_Module_Class* clazz );
Calling this function will do the following:
-
It will check whether the library already holds a module object corresponding to the same module name as the one found in
FT_Module_Class
. -
If this is the case, it will compare the module version number to see whether it is possible to upgrade the module to a new version. If the module class’s version number is smaller than the already installed one, the function returns immediately. Similarly, it checks that the version of FreeType 2 that is running is correct compared to the one required by the module.
-
It creates a new
FT_Module
object, using data and flags of the module class to determine its byte size and how to properly initialize it. -
If a module initializer is present in the module class, it will be called to complete the module object’s initialization.
-
The new module is added to the library’s list of “registered” modules. In case of an upgrade, the previous module object is simply destroyed.
Note that this function doesn’t return an FT_Module
handle, given that module
objects are completely internal to the library (and client applications
shouldn’t normally mess with them :-)
Finally, it is important to understand that FreeType 2 recognizes and manages several kinds of modules. These will be explained in more details later in this document, but we will list for now the following types:
-
Renderer modules are used to convert native glyph images to bitmaps/pixmaps. FreeType 2 comes with two renderer modules by default: one to generate monochrome bitmaps, the other to generate high-quality anti-aliased pixmaps.
-
Font driver modules are used to support one or more font formats. Typically, each font driver provides a specific implementation/derivative of
FT_Face
,FT_Size
,FT_GlyphSlot
, as well asFT_CharMap
. -
Helper modules are shared by several font drivers. For example, the
sfnt
module parses and manages tables found in SFNT-based font formats; it is then used by both the TrueType and OpenType font drivers. -
Finally, the auto-hinter module has a specific place in the library’s design, as its role is to process vectorial glyph outlines, independently of their native font format, to produce optimal results at small pixel sizes.
Note that every FT_Face
object is owned by the corresponding font driver,
depending on the original font file’s format. This means that all face objects
are destroyed when a module is removed/unregistered from a library instance
(typically by calling the FT_Remove_Module()
function).
Because of this, you should always take care that no FT_Face
object is
opened when you upgrade or remove a module from a library, as this could cause
unexpected object deletion!
4. Libraries
We now come back to our well-known FT_Library
object. From what have been
said before, we already know that a library instance owns at least the
following:
-
A memory manager object (
FT_Memory
), used for all allocation/releases within the instance. -
A list of
FT_Module
objects, corresponding to the “installed” or “registered” modules of the instance. This list can be changed at any time throughFT_Add_Module()
andFT_Remove_Module()
. -
Remember that face objects are owner by font drivers that are themselves modules owned by the library.
There is however another object owned by the library instance that hasn’t been described yet: the raster pool.
The raster pool is simply a block of memory of fixed size that is used internally as a “scratch area” for various memory-hungry transient operations, avoiding memory allocation. For example, it is used by each renderer when converting a vectorial glyph outline into a bitmap (actually, that’s where its name comes from :-).
The size of the raster pool is fixed at initialisation time (it defaults to 16kByte) and cannot be changed at run-time (though we could fix this if there is a real need for that).
When a transient operation needs more memory than the pool’s size, it can decide to either allocate a heap block as an exceptional condition, or sub-divide recursively the task to perform in order to never exceed the pool’s threshold.
This extremely memory-conservative behaviour is certainly one of the keys to FreeType’s performance in certain areas (most importantly in glyph rendering/scanline-conversion).
5. Summary
Finally, the following picture illustrates what has been said in this section, as well as the previous, by presenting the complete object graph of FreeType 2’s base design:
IV. Module Classes
We will now try to explain more precisely the types of modules that FreeType 2 is capable of managing. Note that each one of them is described with more details in the following chapters of this document.
-
Renderer modules are used to manage scalable glyph images. This means transforming them, computing their bounding box, and converting them to either monochrome or anti-aliased bitmaps.
Note that FreeType 2 is capable of dealing with any kind of glyph images, as long as a renderer module is provided for it. The library comes by default with two renderers:
Renderer Description raster
Supports the conversion of vectorial outlines (described by a FT_Outline
object) to monochrome bitmaps.smooth
Supports the conversion of the same outlines to high-quality anti-aliased pixmaps (using 256 levels of gray). Note that this renderer also supports direct span generation. -
Font driver modules are used to support one or more specific font format. By default, FreeType 2 comes with the following font drivers:
Driver Description truetype
Supports TrueType font files. type1
Supports Postscript Type 1 fonts, both in binary ( .pfb
) or ASCII (.pfa
) formats, including Multiple Master fonts.cid
Supports Postscript CID-keyed fonts. cff
Supports OpenType, CFF as well as CEF fonts (CEF is a derivative of CFF used by Adobe in its SVG viewer). winfonts
Supports Windows bitmap fonts (i.e. .fon
and.fnt
).Note that font drivers can support bitmapped or scalable glyph images. A given font driver that supports Bézier outlines through
FT_Outline
can also provide its own hinter, or rely on FreeType’sautohinter
module. -
Helper modules are used to hold shared code that is often used by several font drivers, or even other modules. Here are the default helpers:
Helper Description sfnt
Used to support font formats based on the SFNT
storage scheme: TrueType & OpenType fonts as well as other variants (like TrueType fonts that only contain embedded bitmaps)psnames
Used to provide various useful functions related to glyph names ordering and Postscript encodings/charsets. For example, this module is capable of automatically synthetizing a Unicode charmap from a Type 1 glyph name dictionary. psaux
Used to provide various useful functions related to Type 1 charstring decoding, as this “feature” is needed by the type1
,cid
, andcff
drivers. -
Finally, the autohinter module has a specific role in FreeType 2, as it can be used automatically during glyph loading to process individual glyph outlines when a font driver doesn’t provide its own hinting engine.
This module’s purpose and design is also heavily described on the FreeType web site.
We will now study how modules are described, then managed by the library.
1. The FT_Module_Class
structure
As described later in this document, library initialization is performed by
calling the FT_Init_FreeType()
function. The latter is in charge of creating
a new “empty” FT_Library
object, then register each “default” module by
repeatedly calling the FT_Add_Module()
function.
Similarly, client applications can call FT_Add_Module()
any time they wish in
order to register a new module in the library. Let us take a look at this
function’s declaration:
extern FT_Error FT_Add_Module( FT_Library library,
const FT_Module_Class* clazz );
As one can see, this function expects a handle to a library object, as well as
a pointer to a FT_Module_Class
structure. It returns an error code. In case
of success, a new module object is created and added to the library. Note by
the way that the module isn’t returned directly by the call!
Here the definition of FT_Module_Class
, with some explanation. The following
code is taken from <freetype/ftmodule.h>
:
typedef struct FT_Module_Class_
{
FT_ULong module_flags;
FT_Int module_size;
const FT_String* module_name;
FT_Fixed module_version;
FT_Fixed module_requires;
const void* module_interface;
FT_Module_Constructor module_init;
FT_Module_Destructor module_done;
FT_Module_Requester get_interface;
} FT_Module_Class;
A description of its fields:
Field | Description |
---|---|
module_flags |
A set of bit flags used to describe the module’s category. Valid values are: |
ft_module_font_driver if the module is a font driver |
|
ft_module_renderer if the module is a renderer |
|
ft_module_hinter if the module is an auto-hinter |
|
ft_module_driver_scalable if the module is a font driver supporting scalable glyph formats |
|
ft_module_driver_no_outlines if the module is a font driver supporting scalable glyph formats that cannot be described by an FT_Outline object |
|
ft_module_driver_has_hinter if the module is a font driver that provides its own hinting scheme/algorithm |
|
module_size |
An integer that gives the size in bytes of a given module object. This should never be less than sizeof(FT_ModuleRec) , but can be more if the module needs to sub-class the base FT_ModuleRec class. |
module_name |
The module’s internal name, coded as a simple ASCII C string. There can’t be two modules with the same name registered in a given FT_Library object. However, FT_Add_Module() uses the module_version field to detect module upgrades and perform them cleanly, even at run-time. |
module_version |
A 16.16 fixed-point number giving the module’s major and minor version numbers. It is used to determine whether a module needs to be upgraded when calling FT_Add_Module() . |
module_requires |
A 16.16 fixed-point number giving the version of FreeType 2 that is required to install this module. The default value is 0x20000 for FreeType version 2.0 |
module_interface |
Most modules support one or more “interfaces”, i.e. tables of function pointers. This field is used to point to the module’s main interface, if there is one. It is a short-cut that prevents users of the module to call get_interface() each time they need to access one of the object’s common entry points. Note that is is optional, and can be set to NULL. Other interfaces can also be accessed through the get_interface() field. |
module_init |
A pointer to a function used to initialize the fields of a fresh new FT_Module object. It is called after the module’s base fields have been set by the library, and is generally used to initialize the fields of FT_ModuleRec subclasses. Most module classes set it to NULL to indicate that no extra initialization is necessary. |
module_done |
A pointer to a function used to finalize the fields of a given FT_Module object. Note that it is called before the library unsets the module’s base fields, and is generally used to finalize the fields of FT_ModuleRec subclasses. Most module classes set it to NULL to indicate that no extra finalization is necessary |
get_interface |
A pointer to a function used to request the address of a given module interface. Set it to NULL if you don’t need to supportadditional interfaces but the main one. |
2. The FT_Module
type
The FT_Module
type is a handle (i.e. a pointer) to a given module
object/instance, whose base structure is given by the internal FT_ModuleRec
type. We will intentionally not describe this structure here, as there is no
point to look so far into the library’s design.
When FT_Add_Module
is called, it first allocates a new module instance, using
the module_size
class field to determine its byte size. The function
initializes the root FT_ModuleRec
field, then calls the class-specific
initializer module_init
when this field is not set to NULL.
Note that the library defines several sub-classes of FT_ModuleRec
, which are,
as you could have guessed:
-
FT_Renderer
for renderer modules -
FT_Driver
for font driver modules -
FT_AutoHinter
for the auto-hinter
Helper modules use the base FT_ModuleRec
type. We will describe these
classes in the next chapters.