blob: 0841383252f85e4011b7314edf22e32fcad567a4 [file] [log] [blame]
Conventions and Design in the FreeType 2 library
------------------------------------------------
Table of Contents
Introduction
I. Style and Formatting
1. Naming
2. Declarations & Statements
3. Blocks
4. Macros
5. Conventions
II. Design conventions
1. Modularity and Components Layout
2. Configuration and Debugging
III. Usage conventions
1. Error handling
2. Font File I/O
3. Memory management
4. Support for threaded environments
5. Object Management
Introduction
============
This text introduces the many conventions used within the FreeType 2
library code. Please read it before trying any modifications or
extensions of the source code.
I. Style and Formatting
=======================
The following coding rules are extremely important to keep the
library's source code homogeneously. Keep in mind the following
points:
- `Humans read source code, not machines' (Donald Knuth)
The library source code should be as readable as possible, even
by non-C experts. With `readable', two things are meant: First,
the source code should be pleasant to the eye, with sufficient
whitespace and newlines, to not look like a boring stack of
characters stuck to each other. Second, the source should be
_expressive_ enough about its goals. This convention contains
rules that can help the source focus on its purpose, not on a
particular implementation.
- `Paper is the _ultimate_ debugger' (David Turner :-)
There is nothing like sheets of paper (and a large floor) to
help you understand the design of a library you're new to, or to
debug it. The formatting style presented here is targeted at
printing. For example, it is more than highly recommended to
never produce a source line that is wider than 78 columns. More
on this below.
1. Naming
---------
a. Long and expressive labels
Never hesitate to use long labels for your types, variables,
etc.! Except maybe for things like very trivial types, the
longest is the best, as it increases the source's
_expressiveness_. Never forget that the role of a label is to
express the `function' of the entity it represents, not its
implementation!
NOTE: Hungarian notation is NOT expressive, as it sticks the
`type' of a variable to its name. A label like `usFoo'
rarely tells the use of the variable it represents.
And the state of a variable (global, static, dynamic)
isn't helpful anymore.
Conclusion: Avoid Hungarian Notation in FreeType 2.
When forging a name with several nouns
(e.g. `number-of-points'), use an uppercase letter for the first
letter of each word (except the first), like:
numberOfPoints
You are also welcome to introduce underscores `_' in your
labels, especially when sticking large nouns together, as it
`airs' the code greatly. E.g.:
`numberOfPoints' or `number_Of_Points'
`IncredibleFunction' or `Incredible_Function'
And finally, always put a capital letter after an underscore,
except in variable labels that are all lowercase:
`number_of_points' is OK for a variable (_all_ lowercase label)
`incredible_function' is NOT for a function!
^ ^
`Microsoft_windows' is a *shame*!
^ ^
`Microsoft_Windows' isn't really better, but at least its a
^ ^ correct function label within this
convention ;-)
b. Data types
Try to use C types to the very least! Rely on internally
defined equivalent types instead. For example, not all
compilers agree on the sign of `char'; the size of `int' is
platform-specific, etc.
There are equivalents to the most common types in the
`fttypes.h' public header file, like `FT_Short', `FT_UShort',
etc. Using the internal types will guarantee that you won't
need to replace every occurence of `short' or whatever when
compiling on a weird platform or with a weird compiler, and
there are many more than you could think of...
c. Functions
The name of a function should always begin with a capital
letter, as lowercase first letters are reserved for variables.
The name of a function should be, again, _expressive_! Never
hesitate to put long function names in your code: It will make
the code much more readable.
Expressiveness doesn't necessarily imply lengthiness though; for
instance, reading various data types from a file stream is
performed using the following functions defined in the
`ftstream.c' file of the `base' module:
FT_Get_Char(), FT_Get_Short(), FT_Get_Long(), etc.
Which is somewhat more readable than:
cget, sget, usget, lget, etc.
d. Variables
Variable names (at least meant for the public interface) should
always begin with a lowercase letter. Lowercase first letters
are reserved for variables in this convention, as it has been
already explained above. You are still welcome to use long and
expressive variable names.
Something like `numP' can express a number of pixels, porks,
pancakes, and much more... Something like `num_points' won't.
Unfortunately (mostly due to the lazyness of the developers),
short variable names are still used in many parts of the
library. Volunteers are highly welcome to improve this...
As a side note, a field name of a structure counts as a variable
name too.
2. Declarations & Statements
----------------------------
Try to align declarations and assignments in columns, if it proves
logically. For example (taken from `ttraster.c'):
struct TProfile_
{
FT_F26Dot6 X; /* current coordinate during sweep */
PProfile link; /* link to next profile - various purpose */
PLong offset; /* start of profile's data in render pool */
Int flow; /* profile orientation: asc/descending */
Long height; /* profile's height in scanlines */
Long start; /* profile's starting scanline */
UShort countL; /* number of lines to step before this */
/* profile becomes drawable */
PProfile next; /* next profile in same contour, used */
/* during drop-out control */
};
instead of
struct TProfile_
{
FT_F26Dot6 X; /* current coordinate during sweep */
PProfile link; /* link to next profile - various purpose */
PLong offset; /* start of profile's data in render pool */
Int flow; /* profile orientation: asc/descending */
Long height; /* profile's height in scanlines */
Long start; /* profile's starting scanline */
UShort countL; /* number of lines to step before this */
/* profile becomes drawable */
PProfile next; /* next profile in same contour, used */
/* during drop-out control */
};
This comes from the fact that you are more interested in the field
and its function than in its type.
Or:
x = i + 1;
y += j;
min = 100;
instead of
x=i+1;
y+=j;
min=100;
And don't hesitate to separate blocks of declarations with
newlines to `distinguish' logical sections.
E.g., taken from an old source file, in the declarations of the
CMap loader:
long n, num_SH;
unsigned short u;
long off;
unsigned short l;
long num_Seg;
unsigned short* glArray;
long table_start;
int limit, i;
TCMapDir cmap_dir;
TCMapDirEntry entry_;
PCMapTable Plcmt;
PCMap2SubHeader Plcmsub;
PCMap4 Plcm4;
PCMap4Segment segments;
instead of
long n, num_SH;
unsigned short u;
long off;
unsigned short l;
long num_Seg;
unsigned short *glArray;
long table_start;
int limit, i;
TCMapDir cmap_dir;
TCMapDirEntry entry_;
PCMapTable Plcmt;
PCMap2SubHeader Plcmsub;
PCMap4 Plcm4;
PCMap4Segment segments;
3. Blocks
---------
Block separation is done with `{' and `}'. We do not use the K&R
convention which becomes only useful with an extensive use of
tabs. The `{' and its corresponding `}' should always be on the
same column. It makes it easier to separate a block from the rest
of the source, and it helps your _brain_ associate the accolades
easily (ask any Lisp programmer on the topic!).
Use two spaces for the next indentation level.
Never use tabs in FreeType 2 code; their widths may vary with
editors and systems.
Example:
if (condition_test) {
waow mamma;
I'm doing K&R format;
just like the Linux kernel;
} else {
This test failed poorly;
}
should be rather formatted as
if ( condition_test )
{
This code isn't stuck to the condition;
read it on paper, you will find it more;
pleasant to the eye;
}
else
{
Of course, this is a matter of taste;
This is just the way it is in this convention;
and you should follow it to be homogenuous with;
the rest of the FreeType code;
}
4. Macros
---------
Macros should be made of uppercase letters. If a macro label is
forged from several words, it is possible to only uppercasify the
first word, using an underscore to separate the nouns. This is
used in in some files for macros like
GET_UShort(), USE_Stream(), etc.
The role of macros used throughout the engine is explained later
in this document.
5. Conventions
--------------
Currently, FreeType 2 source code uses the following formatting
rules:
. The data type is separated with two spaces from the variable,
structure, or function name:
const char foo;
Usually, the `*' operator is concatenated to the data type:
FT_Int* pointer;
However, when declaring resp. defining an `output' parameter
(i.e. a pointer which will be assigned by the function), the
last `*' must be placed on the right in order to denote this, as
in:
FT_New_Face( FT_Library library,
FT_Face *aface );
where the `*' is used to indicate that `aface' is returned. In
most cases, the name of such an output variable starts with `a'
or `an' (`aface' instead of `face', `anlru' instead of `lru',
etc.), following the English rules of the indefinite article.
. As mentioned above, multiple declarations are vertically
aligned:
FT_Short foo;
FT_Long bar;
FT_GlyphSlot slot;
. Declarations are separated with two blank lines from the
following code. This intentionally disturbs the code flow to
make variable definitions more visible.
{
char x, y;
x = 3;
y = 5;
}
. An opening parenthesis follows a function directly without
space; after a built-in C keyword, one space is used:
x = sin( y );
y = sizeof ( long );
Except for casts, empty parameters, and the closing semicolon,
parentheses are surrounded with space:
x = (char*)( foo + bar );
y = rand();
. Binary operators are surrounded by spaces; unary operators have
no space after it:
x = ( 3 + 4 ) / ( 7 - 2 );
y = -( 3 + 4 ) * 7;
. Array arguments are not surrounded by spaces:
array[3] = array[1] + array[2];
array[4] = array[1 + 3];
. Comma and semicolon have only space at the right side:
if ( x = 0; x < y; x++, y-- )
do_something();
Exception:
for (;;)
{
...
. Don't use
if ( x == y ) a = b;
but
if ( x == y )
a = b;
in general.
. Preprocessor directives are never indented and always start in
the first column.
. All function/structure/variable definitions start at column
three.
. All full-line comments (except the header of a file) start at
column three (even comments for preprocessor directives).
. Labels are sticking out two positions to the left:
switch ( x )
{
case 1:
do_something();
break;
default:
do_nothing();
break;
}
II. Design Conventions
======================
1. Modularity and Components Layout
-----------------------------------
The FreeType 2 engine has been designed with portability in mind.
This implies the ability to compile and run it on a great variety
of systems and weird environments, unlike many packages where the
word strictly means `runs on a bunch of Unix-like systems'. We
have thus decided to stick to the following restrictions:
- The C version is written entirely in ANSI C.
- The library, if compiled with gcc, doesn't produce any warning
with the `-ansi -pedantic' flags. Other compilers with better
checks may produce ANSI warnings -- please report.
(NOTE: It can of course be compiled by an `average' C compiler,
and even by a C++ one.)
- It only requires in its simplest form an ANSI libc to compile,
and no utilities other than a C preprocessor, compiler, and
linker.
- It consists of modules, starting with a `base' module which
provides the API, some auxiliary modules used by the font
drivers, the font driver modules itself, and the rasterizer
modules.
- The very low-level components can be easily replaced by
system-specific ones that do not rely on the standard libc.
These components deal mainly with i/o, memory, and mutex
operations.
- A client application only needs to include one header file named
`freetype.h' to use the engine. Other public header files like
`ftglyph.h' or `ftimage.h' provide functional extensions.
- All configuration options are gathered in two files,
`ftconfig.h' and `ftoption.h'. The former contains the
processor and OS specific configuration options, while the
latter treats options that may be enabled or disabled by the
user to enable and disable various features.
2. Configuration and Debugging
------------------------------
Configuration is covered by the `BUILD' documentation file.
Debugging is controlled by two macros in `ftoption.h',
FT_DEBUG_LEVEL_ERROR and FT_DEBUG_LEVEL_TRACE; don't use them in
code to be released. Check the source code of the `ftview.c'
demonstration program (in the `ft2demos' package) how tracing can
be used and activated.
III. Usage conventions
======================
1. Error Handling
-----------------
Most functions directly return an error code. A return value of 0
(FT_Err_Ok) means that no error occured, while a non-zero other
value indicates a failure of any kind.
We use code like this in FreeType 2:
if ( ( rc = Perform_Action_1( parms_of_1 ) ) ||
( rc = Perform_Action_2( parms_of_2 ) ) ||
( rc = Perform_Action_3( parms_of_3 ) ) )
goto Fail;
which is better but uses assignments within expressions, which are
always delicate to manipulate in C (the risk of writing `=='
exists, and would go unnoticed by a compiler). Moreover, the
assignments are a bit redundant and don't express much things
about the actions performed (they only speak of the error
management issue).
That is why some macros have been defined for the most frequently
used functions. They relate to low-level routines that are called
very often (mainly i/o and memory handling functions). Each macro
produces an implicit assignment to a variable called `error' and
can be used instead as a simple function call. Example:
if ( PERFORM_Action_1( parms_of_1 ) ||
PERFORM_Action_2( parms_of_2 ) ||
PERFORM_Action_3( parms_of_3 ) )
goto Fail;
with
#define PERFORM_Action_1( parms_1 ) \
( error = Perform_Action_1( parms_1 ) )
#define PERFORM_Action_2( parms_1 ) \
( error = Perform_Action_2( parms_1 ) )
#define PERFORM_Action_3( parms_1 ) \
( error = Perform_Action_3( parms_1 ) )
defined in some header file.
There, the developer only needs to define a local `error' variable
and use the macros directly in the code, without caring about the
actual error handling performed. Another advantage is that the
structure of source files remain very similar, even though the
error handling may be different.
This convention is very close to the use of exceptions in
languages like C++, Pascal, Java, etc. where the developer
focuses on the actions to perform, and not on every little error
checking.
2. Font File I/O
----------------
a. Streams
The engine uses `streams' to access the font files. A stream is
a structure containing information used to access files through
a system-specific i/o library.
The default implementation of streams uses the ANSI libc i/o
functions. However, for the sake of embedding in light systems
and independence of a complete C library, it is possible to
re-implement the component for a specific system or OS, letting
it use system calls.
b. Frames
TrueType is tied to the big-endian format, which implies that
reading shorts or longs from the font file may need conversions
depending on the target processor. To be able to easily detect
read errors and allow simple conversion calls or macros, the
engine is able to access a font file using `frames'.
A frame is simply a sequence of successive bytes taken from the
input file at the current position. A frame is pre-loaded into
memory by a call to the `ACCESS_Frame()' macro.
It is then possible to read all sizes of data through the
`GET_xxx()' macros described above.
When all important data is read, the frame can be released by a
call to `FORGET_Frame()'.
The benefits of frames are various. Consider these two
approaches at extracting values:
if ( ( error = Read_Short( &var1 ) ) ||
( error = Read_Long ( &var2 ) ) ||
( error = Read_Long ( &var3 ) ) ||
( error = Read_Short( &var4 ) ) )
return FAILURE;
and
/* Read the next 16 bytes */
if ( ACCESS_Frame( 16L ) )
return error; /* The Frame could not be read */
var1 = GET_Short(); /* extract values from the frame */
var2 = GET_Long();
var3 = GET_Long();
var4 = GET_Short();
FORGET_Frame(); /* release the frame */
In the first case, there are four error assignments with four
checks of the file read. This unnecessarily increases the size
of the generated code. Moreover, you must be sure that `var1'
and `var4' are short variables, `var2' and `var3' long ones, if
you want to avoid bugs and/or compiler warnings.
In the second case, you perform only one check for the read, and
exit immediately on failure. Then the values are extracted from
the frame, as the result of function calls. This means that you
can use automatic type conversion; there is no problem if
e.g. `var1' and `var4' are longs, unlike previously.
Finally, frames are ideal when you are using memory-mapped
files, as the frame is not really `pre-loaded' and never uses
any `heap' space.
IMPORTANT: You CANNOT nest several frame accesses. There is
only one frame available at a time for a specific
instance.
It is also the programmer's responsibility to never
extract more data than was pre-loaded in the frame!
(But you usually know how many values you want to
extract from the file before doing so).
3. Memory Management
--------------------
The library now has a component which uses an interface similar to
malloc()/free().
* FT_Alloc()
To be used like malloc(), except that it returns an error code,
not an address. Its arguments are the size of the requested
block and the address of the target pointer to the `fresh'
block. An error code is returned in case of failure (and this
will also set the target pointer to NULL), 0 in case of success.
FT_Alloc() internally calls the ft_alloc() function defined in
an FT_Memory object. All error checking is done by FT_Alloc()
itself so that ft_alloc() directly calls malloc().
* FT_Realloc()
Similar to FT_Alloc(); it calls realloc() by default.
* FT_Free()
As you may have already guessed, FT_Free() is FT_Alloc()'s
counterpart. It takes as argument the _target pointer's
address_! You should _never_ pass the block's address directly,
i.e. the pointer, to FT_Free().
Similar to FT_Alloc(), FT_Free() does the necessary error
checking and calls free() by default.
As the pointers addresses needed as arguments are typed `void**',
ftmemory.h provides some macros to help use the above functions
more easily, these are:
MEM_Alloc() A version of FT_Alloc() that casts the argument
pointer to (void**). Similar functions are
MEM_Alloc_Array(), MEM_Realloc(), and
MEM_Realloc_Array()
ALLOC() Same as MEM_Alloc(), but with an assignment to a
variable called `error'. See the section `error
handling' above for more info on this. Similar
functions are REALLOC(), ALLOC_ARRAY(), and
REALLOC_ARRAY().
FREE() A version of FT_Free() that casts the argument
pointer to (void**).
MEM_Set() An alias for `memset()', which can be easily
changed to anything else if you wish to use a
different memory manager than the functions
provided by the ANSI libc.
MEM_Copy() An alias of `memcpy()' or `bcopy()' used to move
blocks of memory. You may change it to something
different if necessary (e.g. not using libc).
MEM_Move() An alias of `memmove().' Change its definition if
necessary.
4. Support for threaded environments
------------------------------------
Thread synchronisation has been dropped in FreeType 2. The
library is already re-entrant, and if you really need two threads
accessing the same FT_Library object, you should synchronize
access to it yourself with a simple mutex.
--- end of convntns.txt ---