Search code examples
formulatruetype

What is the formula to get x, y coordinates from a truetype b-spline curve?


What is the formula to draw a line in x, y space given b-spline coordinates in a TrueType font?


Solution

  • Please note that Truetype allows for bspline curves AND straight lines within a glyph definition.

    If you need to change these commands into a series of MoveTo's and LineTo's you can use the following:

    1. Convert the TrueType data into a list of hinted coordinates. This is something your OS can do for you (following code uses the Windows APIs).

    2. Walk all coordinates and translate the curves into lines (see code sniplet below):

      procedure TGlyphEvaluator.EvaluateFromBuffer( Action: TGlyphEvaluatorAction );
      var
          H                                   : TTPOLYGONHEADER;
          C                                   : TTPOLYCURVE;
          Points                              : array of TPointFX;
          P, PE                               : DWORD;
          i, j                                : Integer;
          F                                   : Double;
          PA, PB, PC                          : TPoint;
      begin
          SetLength( Points, 10 );
          P := 0;
          repeat
              // Eat the polygon header
              Move( FBuffer[ P ], H, sizeof( H ) );
              if H.dwType <> TT_POLYGON_TYPE then Break;          // Sanity check!
              PE := P + H.cb;
              Inc( P, sizeof( H ) );
              Points[ 0 ] := H.pfxStart;
              // Eat all the curve records
              while P < PE do begin
                  // Get the curve record
                  Move( FBuffer[ P ], C, sizeof( C ) - sizeof( TPointFX ) );
                  Inc( P, sizeof( C ) - sizeof( TPointFX ) );
      
                  // Get the points from the curve record
                  if Length( Points ) < C.cpfx + 1 then Setlength( Points, C.cpfx + 1 );
                  Move( FBuffer[ P ], Points[ 1 ], sizeof( TPointFX ) * C.cpfx );
                  Inc( P, sizeof( TPointFX ) * C.cpfx );
      
                  case C.wType of
                      TT_PRIM_LINE: begin
                              MoveTo( Action, Points[ 0 ].x.value, Points[ 0 ].y.value );
                              for i := 1 to C.cpfx do
                                  LineTo( Action, Points[ i ].x.value, Points[ i ].y.value );
                          end;
                      TT_PRIM_QSPLINE: begin
                              MoveTo( Action, Points[ 0 ].x.value, Points[ 0 ].y.value );
                              PA.X := Points[ 0 ].x.value;
                              PA.Y := Points[ 0 ].y.value;
                              for i := 1 to C.cpfx - 1 do begin   // DrawQSpline is called C.cpfx - 1 times
                                  PB.X := Points[ i ].x.value;
                                  PB.Y := Points[ i ].y.value;
                                  PC.X := Points[ i + 1 ].x.value;
                                  PC.Y := Points[ i + 1 ].y.value;
                                  if i < C.cpfx - 1 then begin
                                      PC.X := ( PC.X + PB.X ) div 2;
                                      PC.Y := ( PC.Y + PB.Y ) div 2;
                                  end;
                                  for j := 1 to 8 do begin
                                      F := j / 8;
                                      LineTo( Action, Round( ( PA.x - 2 * PB.x + PC.x ) * Sqr( F ) + ( 2 * PB.x - 2 * PA.x ) * F + PA.x ),
                                          Round( ( PA.y - 2 * PB.y + PC.y ) * Sqr( F ) + ( 2 * PB.y - 2 * PA.y ) * F + PA.y ) );
                                  end;
                                  PA := PC;
                              end;
                          end;
                  end;
                  // Update last point.
                  Points[ 0 ] := Points[ C.cpfx ];
              end;
              MoveTo( Action, Points[ 0 ].x.value, Points[ 0 ].y.value );
              LineTo( Action, H.pfxStart.x.value, H.pfxStart.y.value );
          until P >= Longword( Length( FBuffer ) );
      end;