/*
 * gltt graphics library
 * Copyright (C) 1998 Stephane Rehel
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <math.h>

// Reported by <da@skivs.ski.org>
#ifdef WIN32
#include <memory.h>
#endif

#include "FTGlyphPolygonizer.h"
#include "FTGlyph.h"

#include "freetype.h"

#include "GLTTminmax.h"

/////////////////////////////////////////////////////////////////////////////

FTGlyphPolygonizer::FTGlyphPolygonizer()
{
  glyph= 0;

  metrics= new TT_Glyph_Metrics;
  outline= new TT_Outline;

  max_points= 16;
  points= new POINT [ max_points ];
  nPoints= 0;

  precision= 4.;
}

/////////////////////////////////////////////////////////////////////////////

FTGlyphPolygonizer::~FTGlyphPolygonizer()
{
  delete outline;
  outline= 0;

  delete metrics;
  metrics= 0;

  delete points;
  points= 0;
  nPoints= 0;
  max_points= 0;
}

/////////////////////////////////////////////////////////////////////////////

GLTTboolean FTGlyphPolygonizer::init( FTGlyph* _glyph )
{
  glyph= _glyph;

  nContours= 0;
  cur_contour= 0;
  nPoints= 0;

  if( glyph == 0 )
    return 0;

  TT_Error error;

  error= TT_Get_Glyph_Metrics( *glyph->getGlyph(), metrics );
  if( error )
    return GLTT_FALSE;

  error= TT_Get_Glyph_Outline( *glyph->getGlyph(), outline );
  if( error )
    return GLTT_FALSE;

  nContours= outline->contours;

  nPoints= 0;

  return GLTT_TRUE;
}

/////////////////////////////////////////////////////////////////////////////

void FTGlyphPolygonizer::setPrecision( double _precision )
{
  precision= max( _precision, 0.01 );
}

/////////////////////////////////////////////////////////////////////////////

// 0 <= c < nContours
GLTTboolean FTGlyphPolygonizer::getContour( int c )
{
  if( c < 0 || c >= nContours )
    return GLTT_FALSE;

  cur_contour= c;

#define BEZIER(x1,y1,x2,y2,x3,y3)                                   \
          {                                                         \
          double extent= max( fabs(max(x1,x2,x3) - min(x1,x2,x3)),  \
                              fabs(max(y1,y2,y3) - min(y1,y2,y3)) );\
          double _subdivisions= extent / precision + .5;            \
          int subdivisions= int(_subdivisions);                     \
          if( subdivisions > 0 )                                    \
            {                                                       \
            add_point( x1, y1 );                                    \
            double dt= 1. / double(subdivisions);                   \
            register double t= dt;                                  \
            for( int a= 1; a < subdivisions; ++a, t+= dt )          \
              {                                                     \
              register double tt= 1. - t;                           \
              register double t1= tt * tt;                          \
              register double t2= 2. * t * tt;                      \
              register double t3= t * t;                            \
              register double x= t1 * (x1) + t2 * (x2) + t3 * (x3); \
              register double y= t1 * (y1) + t2 * (y2) + t3 * (y3); \
              add_point(x,y);                                       \
              }                                                     \
            }                                                       \
          }

  nPoints= 0;

  int first= (c==0) ? 0 : (outline->conEnds[c-1]+1);
  int last= outline->conEnds[c];

  int k1= first;
  int k2= k1+1;
  int on1= (outline->flag[k1] & 1);
  int on2= (outline->flag[k2] & 1);

  const double scale= 1. / 64.;
  double x1= double(outline->xCoord[k1]) * scale;
  double y1= double(outline->yCoord[k1]) * scale;
  double x2= double(outline->xCoord[k2]) * scale;
  double y2= double(outline->yCoord[k2]) * scale;
  int skip_next= 0;

  for( int k= first+1; k <= last; ++k )
    {
    int k3= (k==last) ? first : (k+1);
    int on3= (outline->flag[k3] & 1);
    double x3= double(outline->xCoord[k3]) * scale;
    double y3= double(outline->yCoord[k3]) * scale;

    if( ! skip_next )
      {
      if( on1 )
        {
        if( on2 )
          {
          add_point(x1,y1);
          if( k == last )
            add_point(x2,y2);
          }
         else
          {
          if( on3 )
            {
            BEZIER(x1,y1,x2,y2,x3,y3);
            if( k == last-1 )
              add_point(x3,y3);
            skip_next= 1;
            }
           else
            {
            double x23= (x2+x3) * .5;
            double y23= (y2+y3) * .5;
            BEZIER(x1,y1,x2,y2,x23,y23);
            }
          }
        }
       else
        {
        if( on2 )
          {
          }
         else
          {
          if( on3 )
            {
            double x12= (x1+x2) * .5;
            double y12= (y1+y2) * .5;
            BEZIER(x12,y12,x2,y2,x3,y3);
            if( k == last-1 )
              add_point(x3,y3);
            skip_next= 1;
            }
           else
            {
            double x12= (x1+x2) * .5;
            double y12= (y1+y2) * .5;
            double x23= (x2+x3) * .5;
            double y23= (y2+y3) * .5;
            BEZIER(x12,y12,x2,y2,x23,y23);
            }
          }
        }
      }

    k1= k2; k2= k3;
    x1= x2; x2= x3;
    y1= y2; y2= y3;
    on1=on2;on2=on3;
    skip_next= 0;
    }

#undef BEZIER

  return GLTT_TRUE;
}

/////////////////////////////////////////////////////////////////////////////

// 0 <= i < nPoints
FTGlyphPolygonizer::POINT* FTGlyphPolygonizer::getContourPoint( int i ) const
{
  if( i < 0 || i >= nPoints || points == 0 )
    return 0;

  return points + i;
}

/////////////////////////////////////////////////////////////////////////////

// 0 <= i < nPoints
void FTGlyphPolygonizer::getContourPoint( int i, double& x, double& y ) const
{
  POINT* p= getContourPoint(i);
  if( p == 0 )
    {
    x= y= 0.;
    return;
    }

  x= p[i].x;
  y= p[i].y;
}

/////////////////////////////////////////////////////////////////////////////

void FTGlyphPolygonizer::add_point( double x, double y )
{
  if( nPoints >= max_points )
    {
    int new_max_points= max_points + max_points / 2;
    POINT* new_points= new POINT [ new_max_points ];
    memcpy( (void*) new_points, (void*) points, nPoints * sizeof(points[0]) );
    points= new_points;
    max_points= new_max_points;
    }

  points[nPoints].x= x;
  points[nPoints].y= y;

  ++nPoints;
}

/////////////////////////////////////////////////////////////////////////////

double FTGlyphPolygonizer::getBearingX() const
{
  return (metrics==0) ? 0 : (double(metrics->bearingX)/64.);
}

/////////////////////////////////////////////////////////////////////////////

double FTGlyphPolygonizer::getBearingY() const
{
  return (metrics==0) ? 0 : (double(metrics->bearingY)/64.);
}

/////////////////////////////////////////////////////////////////////////////

double FTGlyphPolygonizer::getAdvance() const
{
  return (metrics==0) ? 0. : (double(metrics->advance)/64.);
}

/////////////////////////////////////////////////////////////////////////////
