Whole document tree

FreeType 2 Internals

FreeType 2.0 Internals

Version 1.0

© 1999 David Turner (david@freetype.org)
© 1999 The FreeType Development Team (devel@freetype.org)




 

Introduction:

This document describes in great details the internals of the FreeType 2.0 library. It is a must read for porters and developers alike. Its purpose is to present the engine's objects, their roles and interactions. It is assumed that the FreeType Glyph Conventions document has been read.

We advise porters to also read the FreeType Porting Guide after this document. Would-be hackers and maintainers are of course encouraged to read the FreeType Coding Conventions document too. The development of a new driver is described in more details in the FreeType Driver HowTo document.



I. Overview :

1. Features (and what's new) :

FreeType 2.0 has a number of important new features that were not found in the 1.x releases :
 
font-format independent API
FreeType 2.0 is able to support any kind of font format, be it fixed or scalable, through the use of pluggable "font drivers". These drivers can be added or replaced at run time, while applications use a new font format-independent API.

advanced stream caching
2.0 is able to control the number of concurrently opened streams when using fonts. It is thus possible to open dozens or hundreds of font faces without running out of system resources.

real reentrancy support
It is now possible to use FreeType as a shared library with no static data in a multi-threaded environment. The synchronization model has also been simplified in order to make font driver writing easier. Of course, you can build FreeType with no thread support to get a smaller library.

support for cubic beziers and 17-levels anti-aliasing
The FreeType scan-line converter (a.k.a. raster) now supports cubic bezier arcs seamlessly. It also provides a new anti-aliasing mode which uses a palette of 17 levels of grays.
 

It also features the following :
performance improvements :
The FreeType raster has been optimized, and the generation of anti-aliased pixmaps is now 60% faster than in the 1.x release. Moreover, the TrueType bytecode interpreter has been profiled and greatly optimised.

easier portability
Porting and configuring FreeType is now much easier. A single file must be provided for system-specific operations (like memory, i/o, thread management), and a single configuration header is used to select the build you need.
 

2. Architecture :

The engine is now split in several parts, which are :

a. The base layer :

This part contains all the font-format independent features of the engine which are :
  • computations/scaling
  • list processing
  • outline processing
  • scan-line converter
  • stream manager
  • base object classes
  • debugging & traces
  • high-level API functions
  • low-level system object (memory, i/o, threads)

b. The font drivers :

Each font format is managed with the use of a single font driver object. The base layer is able to manage several drivers, and these can be easily added, removed or upgraded at runtime. Each driver has the following features and functions :
  • auto-check font format when opening a font resource (i.e. file)
  • access, load and/or extract all tables and data from the font file
  • grid-fit/hint the glyph outlines (in the case of scalable formats like TrueType or Type1)
  • provide extensions to access font format-specific data and tables from the font file
Note that FreeType 2.0 is a font service. Its purpose is to provide a unified API for all kinds of fonts and extract individual glyph images and metrics. However, it does not render text itself, as this operation is left to the developer, or to higher-level libraries built on top of FreeType. Here are a few features that are thus not implemented :
1) Text string rendering
2) Glyph bitmap/outline caching for improved performance
3) Synthetic fonts (i.e. italicising, emboldening, underlining)
4) Contextual glyph substitution and other advanced layout processes
Note that features 1 through 3 should be provided by the SemTex library, which may soon become part of the standard FreeType distribution.



II. Design :

1. Objects :

They are several kinds of objects in FreeType, which can be described as follows :
Base objects
These objects do not relate directly to font data, but to the way it is organised and managed. It is the basic core and provides functions that are heavily used by each font driver. Examples are the resource objects, used to describe font files, the system object used to manage low-level system operations, or the raster object, used to convert vector outlines into bitmaps or anti-aliased pixmaps. Most of the base objects are not directly visible for client applications of FreeType.

Font objects
The font objects directly model the data as it is found in font files. The root classes implemented in the base layer like FT_Face, FT_Size, FT_GlyphSlot, must be derived in each font driver.

Objects are defined in the files "base/freetype.h" and "base/ftobjs.h". The former contains all the public object definitions usable by client applications. The latter contains private definitions used by the rest of the base layer and each font driver.

2. List management

The "base/ftlist.c" component a very simple doubly-linked list facility which is used by the rest of the engine to create and process lists, including iteration and finalisation. The definition of the list node and functions are placed in the "base/freetype.h" to let client applications access listed objects as they like.

The base list type is FT_List, which links nodes of type FT_ListNode together.
 

3. Limited encapsulation

Unlike what happened in the 1.x releases, the FT_Face, FT_Size, FT_GlyphSlot and FT_CharMap types are no longer blind pointers to opaque types. Rather, the corresponding structures are now public (and defined in "base/freetype.h", see FT_FaceRec, FT_SizeRec, etc..) in order to let client applications read directly the various object attributes they're interested in.

This breaks encapsulation of implementation, famed by OOP, but was chosen because:
 

  • it simplifies a lot the work of client applications and libraries which don't need to perform a function call everytime they want to read one important object attribute (nor does it force them to cache these attributes in their own structures).
  • It reduces greatly the API, as many FT_Get_XXX functions are avoided.
  • Higher-level libraries are able to  access data directly. When it is used frequently, they don't need to cache it in their own structures.
  • It is possible to tightly link FreeType objects with higher-level ones, in a clearer and more efficient way. This is very important when one wants to write a C++ wrapper or a text rendering library on top of FreeType (actually, both projects were performed in an earlier version of FreeType 2.0 which featured classic encapsulation through get/set methods. The resulting code was ugly and slow. Moving to a limited encapsulation approach simplified so many things that the compiled code size was reduced by a factor of two !).
  • Finally, the API and font object structures were designed after the creation of two scalable font drivers and one bitmap font driver. They are now very stable and the public (visible) attributes are not going to change.



III. Base objects :

This section describes the FreeType base object classes :
 

1. System objects :

The system class is in charge of managing all low-level and system-specific operations. This means simply memory management, i/o access and thread synchronisation. It is implemented by the "ftsys.c" component, whose source must be located in the configuration directory when building FreeType. (e.g. "lib/arch/ansi/ftsys.c" for an ANSI build, "lib/arch/unix/ftsys.c" for a Unix one, etc..).

Porting FreeType 2.0 really means providing a new implementation of ftsys (along with a few configuration file changes). Note however that its interface is common to all ports, and located in "base/ftsys.h".

2. Resources and Streams:

The concepts of files as storages, and files as streams has been separated for FreeType 2.0. The "resource" concept was introduced while the "stream" one has been redefined. Here is how they work together :
  • a "resource" is an object which models a file, seen as a storage. There are several classes of resources, which differ usually in two ways : the way their data is accessed by applications, and the way they're named within the system.
    For example, when parsing files with the ANSI C library, data has to be read (through fseek/fread) into intermediate buffers before it can be decoded. This scheme is highly portable, but rather inefficient; when using it, we'll describe the file as a disk-based resource.

    As most modern operating systems now provide memory-mapped files, which allow direct access while improving performance and reducing memory usage. Because data can be read directly in memory, we'll speak of a memory-based resource in this case. For embedded systems (like printers, PDAs, etc..), ROM-fonts fit into this category as well.

    Regarding naming, most systems use a string to name files in their storage hierarchy. Though a typical pathname is an ASCII string ('c:\windows\fonts\times.ttf' on Windows, '/home/fonts/times.ttf' on Unix), some OSes use different schemes, varying from Unicode character strings to file i-node numbers. These details are platform-specific and must be hidden to the rest of the library in resource objects.

    A resource encapsulates the lowest details regarding a file, though it should have NO STATE. Note that the nature or type of a resource (i.e. disk or memory based) is important to the "stream" component only. The rest of the library and font drivers work transparently from their implementation.

    Note also that it is perfectly possible to mix resources of distinct natures in a single build

  • a "stream" is an object which is used to extract bytes from a resource. Only resource objects can create streams, through its Open_Stream() method. A stream has state, which typically consist of a file "cursor", some intermediate buffers, a "current frame" and, of course, methods used to extract the data from streams, resolving endianess and alignement issues.
Data can be extracted from streams through direct reads, or through the use of frames. A frame models a run of contiguous bytes starting from the current stream position, and of liberal size.

Methods exist to extract successive integers of any sizes, while resolving endianess and alignement issues. Rather than a long rethorical explanation, here's how frames are typically used :

{
  …
  FT_Error  error;

  error = FT_Access_Frame( stream, 14 );
  if (error) goto Fail;

  val1 = FT_Get_Short(stream);
  val2 = FT_Get_Long(stream);
  val3 = FT_Get_Long(stream);
  val4 = FT_Get_Long(stream);

  FT_Forget_Frame(stream);
  …
}

This code does the following :
  1.  first, it "loads" the next 14 bytes from the current cursor position into the stream's frame, using the FT_Access_Frame API. An error is returned if, for example, less than 14 bytes are left in the stream when the call occurs..
  1.  it extract four integers (one 16-bit short, three 32-bit longs) from the frame using FT_Get_Short and FT_Get_Long. These function increment the frame's cursor finally, it "releases" the stream's frame.
  1.  Each stream has its own frame which can be accessed independently, however, nested frame accesses are not allowed. Note also that the bytes are effectively read from the stream on the call to FT_Access_Frame. Any subsequent read will occur after these 14 bytes, even if less are extracted through FT_Get_xxxx functions.
The implementation of the resource class is located in the system component (i.e. "arch/<system>/ftsys.c") and can thus be tailored for a specific port of the engine.

A resource can be created through the FT_New_Resource API; however this function only accepts an 8-bit pathname to name the target font file, which may be inappropriate for systems using a different naming scheme (e.g. UTF-16 pathname, i-node number, etc..). It's up to the porter then to provide its own resource creation function (like. FT_New_UTF16_Resource, for example) in its version of "ftsys.c".

Note that FT_New_Resource will fail and return an error code if the font file cannot be found, or when its font format isn't recognized by one of the drivers installed in the library. The list or resources created for a given library instance is thus the list of "installed font files".
 

3. Stream Manager :

As said before, resources do not bear states, while streams do. Stream creation is also a very lengthy process, depending on the target operating system (e.g. "fopen" is usually very slow).

Because a typical font driver will want to use a new stream on each access to individual glyphs, being able to cache the most recently used streams is a requirement in order to avoid considerable performance penalties.

Stream caching is thus implemented in the "ftstream" component. It maintains a simple LRU list of the least recently used streams. Each stream in the cache is still opened and available for immediate processing. When a resource is destroyed, the stream cache is parsed to remove all related cached streams.

Stream caching can also be disabled with a configuration macro when using only ROM based resources (where stream opening is really quick). It is implemented through a Stream Manager object (see ftstream.c).
 

4. Raster :

The raster is the component is charge of generating bitmaps and anti-aliased pixmaps from vectorial outline definitions. It is also sometimes called the scan-line converter. It has been completely rewritten for FreeType 2.0 in order to support third-order bezier arcs, 17-levels anti-aliasing (through 4x4 sub-sampling), improved performance, as well as stand-alone compilation (in order to include it in other graphics package without requiring the rest of the FreeType engine).

Because it was designed for easy re-use and embedded systems, the raster is a rtaher 'unusual' piece of code, because it doesn't perform a single memory allocation, nor contain any static or global variable. Rather, it is up to client applications to allocate a raster object in their own heap or memory space.

Each raster object also needs a rather large block of memory called its render pool. The pool is used during rendering (and only during it) in order to perform the scan-line conversion. Because it accesses and manages data directly within the pool, the raster yelds impressive performance as well as bounded memory consumption. It can also automatically decompose large requests into smaller individual sub-tasks.

Finally, it never creates bitmaps or pixmaps, but simply renders into them (providing clipping too). These must be described to the raster with the help of a FT_Raster_Map structure (a very simple bitmap/pixmap descriptor).

Note that when rendering anti-aliased pixmaps, the raster doesn't use an intermediate bitmap buffer, as filtering is part of the scan-line conversion process.
 

5. Library objects :

A library object models a single instance of the FreeType engine. This is useful when FreeType is compiled as a shared object (DLL), as it can then be used by several applications, each with its own resources and objects.

The FT_Library type is an opaque handle to a library object. Such an object is created through a call  to FT_Init_FreeType. Once you don't need it anymore, one can destroy a library object through FT_Done_FreeType.

Note that in reentrant builds, several threads can access a single library object concurrently. Such a build can be chosen by switching one configuration macro in the file 'arch/<system>/ftconfig.h'

6. Driver objects :

A driver object models an instance of a given font driver, i.e. an element of FreeType code in charge of handling a given font format, like TrueType, Type1, FNT, PCF, etc..

Each library object contains a given set of driver objects when it is created through FT_Init_FreeType, this set being determined at compile time (see the file 'base/ftapi.c'). However, removing or adding drivers is possible at run-time, in order to make upgrades easy.

7. Diagram

This diagram show the object relationships for the sole base layer. The library object is the root of the object graph :

It can be read as follows :
 

  • Each library object has one system, one raster and one stream manager objects. These objects can only belong to one given library.
  • Each library contains one list of 0 or more resources, as well as one list of 0 or more driver objects.
  • Each stream manager holds a bounded list ("0..n" where 'n' is the stream cache's size) of stream objects. Each stream is related to one given resource object. Each resource may be related to zero or one stream.
  • Each resource is related to one driver object. A driver is related to 0 or more resources.



IV. Font objects :

Font objects are used to directly map the information found in font files into several categories :
 

1. Face objects :

Face objects are used to model individual font faces. They encapsulate data which isn't related to a specific character size, or a specific glyph or glyph set. Usually, this means :
  • the font face's family and style names (e.g. "Palatino" + "Regular")
  • some flags indicating which kind of font this is (scalable or fixed ? fixed-width or proportional ? horizontal or vertical ? etc…)
  • the number of glyphs, charmaps and eventually fixed character sizes (for bitmap formats) found in the font face.
  • for scalable formats, some important metrics like the ascender, descender, global font bounding box, maximum advance width, etc.. expressed in notional font/grid units (as well as the number of units on the EM grid).
A face is created from a resource object, with the FT_New_Face API. Each driver contains a list of opened face objects for the resources it manages. When a driver is removed or destroyed, all its child faces are discarded automatically with it.

2. Size objects :

Size objects are used to model a given character dimension for a given device resolution (which really means a given character pixel dimensions).

Each size object is created from a parent face object. The object can be reset to new dimensions at any time. Each face object holds a list of all its child sizes, these are destroyed automatically when the face object is discarded.

The metrics contains metrics, expressed in pixels, for the ascender, descender, maximum advance width, etc..
 

3. Glyph Slot objects :

A glyph slot is a container where one can load individual glyphs, be they in vector of bitmap format. Each slot also contains metrics for the glyph it contains.

Each face object contains one or more glyph slot object : the first glyph slot is created automatically with its parent face, and it is possible to add new glyph slots (this is rarely used outside of debugging purposes).
 

4. CharMap objects :

A charmap object is a sort of dictionary whose task is to translate character codes in a given character encoding (like ShiftJIS, Unicode, ANSI, etc..) into glyph indexes in a given font face.

A face object contains one or more charmap objects. All charmap objects are created when the parent face is created, though they're not directly visible to client applications (rather, they can be enumerated through FT_Get_First_CharMap and FT_Get_Next_CharMap, or more simply picked adequately with FT_Find_CharMap for a set of given encodings).
 

5. Diagram

The following diagram illustrates the relationships between font objects :

Which can be read as :
 

  • each resource may have zero or more child face objects "opened" for it. The number of faces is bounded by the number of font faces within the font resource.
  • each driver holds a list of all the faces opened for the resources it manages. When the driver is removed, its child faces are discarded automatically.
  • each face object has one single parent resource, and one single driver.
  • each face has one or more charmaps, and one or more glyph slots
  • each face holds a list of zero or more child size objects
  • each charmap, glyph slot and size is related to one given parent face. These objects are destroyed automatically when the parent face is discarded.



V. Driver Interface :

A font driver is added to a given library object through the FT_Add_Driver API. This function receives a structure known as a FT_DriverInterface, which describes the driver's basic properties.

The FT_DriverInterface contains a set of function pointers used for the base FreeType functionalities. However, each driver can also provide a font-format-specific extended interface to allow client applications to use more advanced features.
 

1. Common Interface

The structure of FT_DriverInterface is rather simple, and defined in "base/ftdriver.h". It must be well known by any developer who wants to write a new driver for the engine. We advise reading the FreeType Driver HowTo as well as the source code of existing drivers. Source comments.

2. Driver-specific extensions

The field of the FT_DriverInterface structure is a typeless pointer to a format-specific interface. This extended interface is usually a structure containing function pointers as well as other kind of information related to the driver.

It is assumed that client applications that wish to use the driver-specific extensions are able to #include the relevant header files to understand the format-specific interface structure.


VI. Configuration:

This section relates to the configuration of the FreeType library. By configuration, we mean selection of build options as well as the choice of font drivers to be used for each new library object.
 

1. Configuration files :

A single file is used to configure the FreeType base engine. As it is considered system-specific, it is located in the architecture directories of the library, under the name "arch/<system>/ftconfig.h". Note that the same directory should also contain a platform-specific implementation of "ftsys.c".

The configuration files is a simple C header which is included by the engine's sources during compilation. It is not included in "freetype.h", and hence doesn't need to be copied when installing the FreeType headers on your system.

It is made of a series of #define or #undef statements, which are used to select or turn off a specific option. Each option is documented with heavy comments, and some of them are explained below.

2. Building and Makefiles :

FreeType 2.0 is more complex than its 1.x release. In order to facilitate maintenance, as well as ease considerably the writing of new font drivers, only GNU Make is supported with FreeType 2.0. However, it is possible to use any compiler, as well as any object or library prefix (.o, .obj, .a, .lib etc..) with them.

To build FreeType 2.0, one has to be in the library directory, then invoke its platform-specific makefile. For a Unix system, this would be :

% cd freetype2/lib
% make -f arch/unix/Makefile

where 'make' is really GNU Make !

The system-specific Makefile located in 'arch/<system>' is a tiny file used to define several variables. It then includes the file freetype2/lib/Makefile.lib, which contains all the gory details about library compilation. The system-specific Makefile can be very easily modified to accomodate a new compiler/platform (see the comments within one of these files).

Each font driver is located in a directory like "freetype2/lib/drivers/<formatdir>". For example, the TrueType driver is located in "drivers/truetype". Each driver directory must contain a Makefile which will be included by Makefile.lib. The former is used to define and build driver object files.
 


3. Make options :

The base layer, as well as each font driver, are made up of several C sources. Traditionally, one compiles each source (i.e. '.c' file) into an object ('.o' or '.obj') file, and all of them are grouped into a library file (i.e. '.a' or '.lib').

By default, FreeType takes a slightly different approach when it comes to compiling each part of the engine. Usually, a single tiny source is compiled, which includes all other component sources. This results in a single object files, with the benefits or reduced code size, usually better compilation as well as a drastic reduction of the number of symbols exported by the library. Of course, it is made possible through the use of specific declaration macros in the FreeType source (see the definition of LOCAL_DEF and LOCAL_FUNC in ftconfig.h for details).

For a concrete example, see the source code in "base/ftbase.c" which generates the whole base layer in a single object file. The same build process is applied to font drivers, in order to generate one single object file per given font format (e.g. truetype.o, type1.o, etc..).

Compiling the library and drivers in "normal" mode is possible, through the use of the 'multi' target (which really means « multiple objects »). For example, calling :

% make -f arch/ansi/Makefile multi
Will build the FreeType library by compiling each source file to an individual object, then linking them together. You'll notice that the library is significantly bigger in this case. Creating a shared dll from a 'multi' build is certainly a very poor idea, as this will export a huge quantity of symbols that aren't useful to any client application.

4. Adding a driver at compile time

A driver can be included very easily in the build process by including its Makefile in Makefile.lib. For example, the TrueType driver is simply included with the following lines (see Makefile.lib):
# TrueType driver rules
#
include $(DRIVERS_DIR)/truetype/Makefile


Where DRIVERS_DIR really is "freetype2/lib/drivers", though this can be redefined. You can, of course specify a different path if you want to place your driver sources in another location.

Note that this only adds the driver's object files to the generated library file. A few more steps are needed to make your FT_Library objects use the driver. They consist in modifying the file "base/ftinit.c", whose sole purpose is to define the set of driver objects that are to be created with each new library object.
 

5. Adding a driver at run time

New driver objects can be added at run-time through the FT_Add_Driver API. This function takes a handle to an existing library object, as well as a pointer to a given driver interface. This interface is used to create a new driver object and register it within the library.

Similarly, a single driver can be removed from a library anytime through FT_Remove_Driver. This will automatically discard the resources and face objects managed by the driver.

6. Custom library objects :

Finally, it is possible to build custom library objects. You need to pass a handle to a valid FT_System object to the FT_Build_Library API. The function will return a handle to the new fresh library object. Note that the library has no registered drivers after the call, developers have to add them by hand with FT_Add_Driver.

It is thus possible to create two distinct library objects with distinct FT_System implementations in the same session, which can be useful for debugging purpose.