shithub: freetype+ttf2subf

ref: 27c8827f3c2c5270d8730a9869a01b22a7ff8ac4
dir: /docs/convntns.txt/

View raw version

          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 ---