/***************************************************************************/
/*                                                                         */
/*  ahhint.c                                                               */
/*                                                                         */
/*    Glyph hinter (body).                                                 */
/*                                                                         */
/*  Copyright 2000 Catharon Productions Inc.                               */
/*  Author: David Turner                                                   */
/*                                                                         */
/*  This file is part of the Catharon Typography Project and shall only    */
/*  be used, modified, and distributed under the terms of the Catharon     */
/*  Open Source License that should come with this file under the name     */
/*  `CatharonLicense.txt'.  By continuing to use, modify, or distribute    */
/*  this file you indicate that you have read the license and              */
/*  understand and accept it fully.                                        */
/*                                                                         */
/*  Note that this license is compatible with the FreeType license.        */
/*                                                                         */
/***************************************************************************/


#include "ahhint.h"
#include "ahglyph.h"
#include "ahangles.h"

#include "ftoutln.h"


#define FACE_GLOBALS( face )  ( (AH_Face_Globals*)( face )->autohint.data )

#define AH_USE_IUP


/*************************************************************************/
/*************************************************************************/
/****                                                                 ****/
/****   Hinting routines                                              ****/
/****                                                                 ****/
/*************************************************************************/
/*************************************************************************/


static int disable_horz_edges = 0;
static int disable_vert_edges = 0;


/* snap a given width in scaled coordinates to one of the */
/* current standard widths                                */
static
FT_Pos  ah_snap_width( FT_Pos*  widths,
					   FT_Int count,
					   FT_Pos width ) {
	int n;
	FT_Pos best      = 64 + 32 + 2;
	FT_Pos reference = width;


	for ( n = 0; n < count; n++ )
	{
		FT_Pos w;
		FT_Pos dist;


		w = widths[n];
		dist = width - w;
		if ( dist < 0 ) {
			dist = -dist;
		}
		if ( dist < best ) {
			best      = dist;
			reference = w;
		}
	}

	if ( width >= reference ) {
		width -= 0x21;
		if ( width < reference ) {
			width = reference;
		}
	} else
	{
		width += 0x21;
		if ( width > reference ) {
			width = reference;
		}
	}

	return width;
}


/* align one stem edge relative to the previous stem edge */
static
void  ah_align_linked_edge( AH_Hinter*  hinter,
							AH_Edge*    base_edge,
							AH_Edge*    stem_edge,
							int vertical ) {
	FT_Pos dist    = stem_edge->opos - base_edge->opos;
	AH_Globals*  globals = &hinter->globals->scaled;
	FT_Pos sign    = 1;


	if ( dist < 0 ) {
		dist = -dist;
		sign = -1;
	}

	if ( vertical ) {
		dist = ah_snap_width( globals->heights, globals->num_heights, dist );

		/* in the case of vertical hinting, always round */
		/* the stem heights to integer pixels            */
		if ( dist >= 64 ) {
			dist = ( dist + 16 ) & - 64;
		} else {
			dist = 64;
		}
	} else
	{
		dist = ah_snap_width( globals->widths,  globals->num_widths, dist );

		if ( hinter->flags & ah_hinter_monochrome ) {
			/* monochrome horizontal hinting: snap widths to integer pixels */
			/* with a different threshold                                   */
			if ( dist < 64 ) {
				dist = 64;
			} else {
				dist = ( dist + 32 ) & - 64;
			}
		} else
		{
			/* for horizontal anti-aliased hinting, we adopt a more subtle */
			/* approach: we strengthen small stems, round stems whose size */
			/* is between 1 and 2 pixels to an integer, otherwise nothing  */
			if ( dist < 48 ) {
				dist = ( dist + 64 ) >> 1;
			} else if ( dist < 128 ) {
				dist = ( dist + 42 ) & - 64;
			}
		}
	}

	stem_edge->pos = base_edge->pos + sign * dist;
}


static
void  ah_align_serif_edge( AH_Hinter*  hinter,
						   AH_Edge*    base,
						   AH_Edge*    serif ) {
	FT_Pos dist;
	FT_Pos sign = 1;

	UNUSED( hinter );


	dist = serif->opos - base->opos;
	if ( dist < 0 ) {
		dist = -dist;
		sign = -1;
	}

	/* do not strengthen serifs */
	if ( base->flags & ah_edge_done ) {
		if ( dist > 64 ) {
			dist = ( dist + 16 ) & - 64;
		} else if ( dist <= 32 ) {
			dist = ( dist + 33 ) >> 1;
		}
	}

	serif->pos = base->pos + sign * dist;
}


/*************************************************************************/
/*************************************************************************/
/*************************************************************************/
/****                                                                 ****/
/****       E D G E   H I N T I N G                                   ****/
/****                                                                 ****/
/*************************************************************************/
/*************************************************************************/
/*************************************************************************/


/* Another alternative edge hinting algorithm */
static
void  ah_hint_edges_3( AH_Hinter*  hinter ) {
	AH_Edge*     edges;
	AH_Edge*     edge_limit;
	AH_Outline*  outline = hinter->glyph;
	FT_Int dimension;


	edges      = outline->horz_edges;
	edge_limit = edges + outline->num_hedges;

	for ( dimension = 1; dimension >= 0; dimension-- )
	{
		AH_Edge*  edge;
		AH_Edge*  before = 0;
		AH_Edge*  after  = 0;
		AH_Edge*  anchor = 0;
		int has_serifs = 0;


		if ( disable_vert_edges && !dimension ) {
			goto Next_Dimension;
		}

		if ( disable_horz_edges && dimension ) {
			goto Next_Dimension;
		}

		/* we begin by aligning all stems relative to the blue zone */
		/* if needed -- that's only for horizontal edges            */
		if ( dimension ) {
			for ( edge = edges; edge < edge_limit; edge++ )
			{
				FT_Pos*  blue;
				AH_Edge  *edge1, *edge2;


				if ( edge->flags & ah_edge_done ) {
					continue;
				}

				blue  = edge->blue_edge;
				edge1 = 0;
				edge2 = edge->link;

				if ( blue ) {
					edge1 = edge;
				} else if ( edge2 && edge2->blue_edge )     {
					blue  = edge2->blue_edge;
					edge1 = edge2;
					edge2 = edge;
				}

				if ( !edge1 ) {
					continue;
				}

				edge1->pos    = blue[0];
				edge1->flags |= ah_edge_done;

				if ( edge2 && !edge2->blue_edge ) {
					ah_align_linked_edge( hinter, edge1, edge2, dimension );
					edge2->flags |= ah_edge_done;
				}

				if ( !anchor ) {
					anchor = edge;
				}
			}
		}

		/* now, we will align all stem edges, trying to maintain the */
		/* relative order of stems in the glyph..                    */
		before = 0;
		after  = 0;
		for ( edge = edges; edge < edge_limit; edge++ )
		{
			AH_Edge  *edge2;


			if ( edge->flags & ah_edge_done ) {
				continue;
			}

			/* skip all non-stem edges */
			edge2 = edge->link;
			if ( !edge2 ) {
				has_serifs++;
				continue;
			}

			/* now, align the stem */

			/* this should not happen, but it's better to be safe.. */
			if ( edge2->blue_edge || edge2 < edge ) {

#if 0
				printf( "strange blue alignement, edge %d to %d\n",
						edge - edges, edge2 - edges );
#endif

				ah_align_linked_edge( hinter, edge2, edge, dimension );
				edge->flags |= ah_edge_done;
				continue;
			}

			{
				FT_Bool min = 0;
				FT_Pos delta;

				if ( !anchor ) {
					edge->pos = ( edge->opos + 32 ) & - 64;
					anchor    = edge;
				} else {
					edge->pos = anchor->pos +
								( ( edge->opos - anchor->opos + 32 ) & - 64 );
				}

				edge->flags |= ah_edge_done;

				if ( edge > edges && edge->pos < edge[-1].pos ) {
					edge->pos = edge[-1].pos;
					min       = 1;
				}

				ah_align_linked_edge( hinter, edge, edge2, dimension );
				delta = 0;
				if ( edge2 + 1 < edge_limit        &&
					 edge2[1].flags & ah_edge_done ) {
					delta = edge2[1].pos - edge2->pos;
				}

				if ( delta < 0 ) {
					edge2->pos += delta;
					if ( !min ) {
						edge->pos += delta;
					}
				}
				edge2->flags |= ah_edge_done;
			}
		}

		if ( !has_serifs ) {
			goto Next_Dimension;
		}

		/* now, hint the remaining edges (serifs and single) in order */
		/* to complete our processing                                 */
		for ( edge = edges; edge < edge_limit; edge++ )
		{
			if ( edge->flags & ah_edge_done ) {
				continue;
			}

			if ( edge->serif ) {
				ah_align_serif_edge( hinter, edge->serif, edge );
			} else if ( !anchor )   {
				edge->pos = ( edge->opos + 32 ) & - 64;
				anchor    = edge;
			} else {
				edge->pos = anchor->pos +
							( ( edge->opos - anchor->opos + 32 ) & - 64 );
			}

			edge->flags |= ah_edge_done;

			if ( edge > edges && edge->pos < edge[-1].pos ) {
				edge->pos = edge[-1].pos;
			}

			if ( edge + 1 < edge_limit        &&
				 edge[1].flags & ah_edge_done &&
				 edge->pos > edge[1].pos      ) {
				edge->pos = edge[1].pos;
			}
		}

Next_Dimension:
		edges      = outline->vert_edges;
		edge_limit = edges + outline->num_vedges;
	}
}


LOCAL_FUNC
void  ah_hinter_hint_edges( AH_Hinter*  hinter,
							int no_horz_edges,
							int no_vert_edges ) {
	disable_horz_edges = no_horz_edges;
	disable_vert_edges = no_vert_edges;

	/* AH_Interpolate_Blue_Edges( hinter ); -- doesn't seem to help      */
	/* reduce the problem of the disappearing eye in the `e' of Times... */
	/* also, creates some artifacts near the blue zones?                 */
	{
		ah_hint_edges_3( hinter );

#if 0
		/* outline optimizer removed temporarily */
		if ( hinter->flags & ah_hinter_optimize ) {
			AH_Optimizer opt;


			if ( !AH_Optimizer_Init( &opt, hinter->glyph, hinter->memory ) ) {
				AH_Optimizer_Compute( &opt );
				AH_Optimizer_Done( &opt );
			}
		}
#endif

	}
}


/*************************************************************************/
/*************************************************************************/
/*************************************************************************/
/****                                                                 ****/
/****       P O I N T   H I N T I N G                                 ****/
/****                                                                 ****/
/*************************************************************************/
/*************************************************************************/
/*************************************************************************/

static
void  ah_hinter_align_edge_points( AH_Hinter*  hinter ) {
	AH_Outline*  outline = hinter->glyph;
	AH_Edge*     edges;
	AH_Edge*     edge_limit;
	FT_Int dimension;


	edges      = outline->horz_edges;
	edge_limit = edges + outline->num_hedges;

	for ( dimension = 1; dimension >= 0; dimension-- )
	{
		AH_Edge*   edge;
		AH_Edge*   before;
		AH_Edge*   after;


		before = 0;
		after  = 0;

		edge = edges;
		for ( ; edge < edge_limit; edge++ )
		{
			/* move the points of each segment     */
			/* in each edge to the edge's position */
			AH_Segment*  seg = edge->first;


			do
			{
				AH_Point*  point = seg->first;


				for (;; )
				{
					if ( dimension ) {
						point->y      = edge->pos;
						point->flags |= ah_flah_touch_y;
					} else
					{
						point->x      = edge->pos;
						point->flags |= ah_flah_touch_x;
					}

					if ( point == seg->last ) {
						break;
					}

					point = point->next;
				}

				seg = seg->edge_next;

			} while ( seg != edge->first );
		}

		edges      = outline->vert_edges;
		edge_limit = edges + outline->num_vedges;
	}
}


/* hint the strong points -- this is equivalent to the TrueType `IP' */
static
void  ah_hinter_align_strong_points( AH_Hinter*  hinter ) {
	AH_Outline*  outline = hinter->glyph;
	FT_Int dimension;
	AH_Edge*     edges;
	AH_Edge*     edge_limit;
	AH_Point*    points;
	AH_Point*    point_limit;
	AH_Flags touch_flag;


	points      = outline->points;
	point_limit = points + outline->num_points;

	edges       = outline->horz_edges;
	edge_limit  = edges + outline->num_hedges;
	touch_flag  = ah_flah_touch_y;

	for ( dimension = 1; dimension >= 0; dimension-- )
	{
		AH_Point*  point;
		AH_Edge*   edge;
		AH_Edge*   before;
		AH_Edge*   after;


		before = 0;
		after  = 0;

		if ( edges < edge_limit ) {
			for ( point = points; point < point_limit; point++ )
			{
				FT_Pos u, ou, fu; /* point position */
				FT_Pos delta;


				if ( point->flags & touch_flag ) {
					continue;
				}

#ifndef AH_OPTION_NO_WEAK_INTERPOLATION
				/* if this point is candidate to weak interpolation, we will  */
				/* interpolate it after all strong points have been processed */
				if ( point->flags & ah_flah_weak_interpolation ) {
					continue;
				}
#endif

				if ( dimension ) {
					u  = point->fy;
					ou = point->oy;
				} else
				{
					u  = point->fx;
					ou = point->ox;
				}

				fu = u;

				/* is the point before the first edge? */
				edge  = edges;
				delta = edge->fpos - u;
				if ( delta >= 0 ) {
					u = edge->pos - ( edge->opos - ou );
					goto Store_Point;
				}

				/* is the point after the last edge ? */
				edge  = edge_limit - 1;
				delta = u - edge->fpos;
				if ( delta >= 0 ) {
					u = edge->pos + ( ou - edge->opos );
					goto Store_Point;
				}

				/* otherwise, interpolate the point in between */
				{
					AH_Edge*  before = 0;
					AH_Edge*  after  = 0;


					for ( edge = edges; edge < edge_limit; edge++ )
					{
						if ( u == edge->fpos ) {
							u = edge->pos;
							goto Store_Point;
						}
						if ( u < edge->fpos ) {
							break;
						}
						before = edge;
					}

					for ( edge = edge_limit - 1; edge >= edges; edge-- )
					{
						if ( u == edge->fpos ) {
							u = edge->pos;
							goto Store_Point;
						}
						if ( u > edge->fpos ) {
							break;
						}
						after = edge;
					}

					/* assert( before && after && before != after ) */
					u = before->pos + FT_MulDiv( fu - before->fpos,
												 after->pos - before->pos,
												 after->fpos - before->fpos );
				}

Store_Point:

				/* save the point position */
				if ( dimension ) {
					point->y = u;
				} else {
					point->x = u;
				}

				point->flags |= touch_flag;
			}
		}

		edges      = outline->vert_edges;
		edge_limit = edges + outline->num_vedges;
		touch_flag = ah_flah_touch_x;
	}
}


#ifndef AH_OPTION_NO_WEAK_INTERPOLATION

static
void  ah_iup_shift( AH_Point*  p1,
					AH_Point*  p2,
					AH_Point*  ref ) {
	AH_Point*  p;
	FT_Pos delta = ref->u - ref->v;


	for ( p = p1; p < ref; p++ )
		p->u = p->v + delta;

	for ( p = ref + 1; p <= p2; p++ )
		p->u = p->v + delta;
}


static
void  ah_iup_interp( AH_Point*  p1,
					 AH_Point*  p2,
					 AH_Point*  ref1,
					 AH_Point*  ref2 ) {
	AH_Point*  p;
	FT_Pos u;
	FT_Pos v1 = ref1->v;
	FT_Pos v2 = ref2->v;
	FT_Pos d1 = ref1->u - v1;
	FT_Pos d2 = ref2->u - v2;


	if ( p1 > p2 ) {
		return;
	}

	if ( v1 == v2 ) {
		for ( p = p1; p <= p2; p++ )
		{
			FT_Pos u = p->v;


			if ( u <= v1 ) {
				u += d1;
			} else {
				u += d2;
			}

			p->u = u;
		}
		return;
	}

	if ( v1 < v2 ) {
		for ( p = p1; p <= p2; p++ )
		{
			u = p->v;

			if ( u <= v1 ) {
				u += d1;
			} else if ( u >= v2 ) {
				u += d2;
			} else {
				u = ref1->u + FT_MulDiv( u - v1, ref2->u - ref1->u, v2 - v1 );
			}

			p->u = u;
		}
	} else
	{
		for ( p = p1; p <= p2; p++ )
		{
			u = p->v;

			if ( u <= v2 ) {
				u += d2;
			} else if ( u >= v1 ) {
				u += d1;
			} else {
				u = ref1->u + FT_MulDiv( u - v1, ref2->u - ref1->u, v2 - v1 );
			}

			p->u = u;
		}
	}
}


/* interpolate weak points -- this is equivalent to the TrueType `IUP' */
static
void  ah_hinter_align_weak_points( AH_Hinter*  hinter ) {
	AH_Outline*  outline = hinter->glyph;
	FT_Int dimension;
	AH_Edge*     edges;
	AH_Edge*     edge_limit;
	AH_Point*    points;
	AH_Point*    point_limit;
	AH_Point**   contour_limit;
	AH_Flags touch_flag;


	points      = outline->points;
	point_limit = points + outline->num_points;

	/* PASS 1: Move segment points to edge positions */

	edges      = outline->horz_edges;
	edge_limit = edges + outline->num_hedges;
	touch_flag = ah_flah_touch_y;

	contour_limit = outline->contours + outline->num_contours;

	ah_setup_uv( outline, ah_uv_oy );

	for ( dimension = 1; dimension >= 0; dimension-- )
	{
		AH_Point*   point;
		AH_Point*   end_point;
		AH_Point*   first_point;
		AH_Point**  contour;


		point   = points;
		contour = outline->contours;

		for ( ; contour < contour_limit; contour++ )
		{
			point       = *contour;
			end_point   = point->prev;
			first_point = point;

			while ( point <= end_point && !( point->flags & touch_flag ) )
				point++;

			if ( point <= end_point ) {
				AH_Point*  first_touched = point;
				AH_Point*  cur_touched   = point;


				point++;
				while ( point <= end_point )
				{
					if ( point->flags & touch_flag ) {
						/* we found two successive touched points; we interpolate */
						/* all contour points between them                        */
						ah_iup_interp( cur_touched + 1, point - 1,
									   cur_touched, point );
						cur_touched = point;
					}
					point++;
				}

				if ( cur_touched == first_touched ) {
					/* this is a special case: only one point was touched in the */
					/* contour; we thus simply shift the whole contour           */
					ah_iup_shift( first_point, end_point, cur_touched );
				} else
				{
					/* now interpolate after the last touched point to the end */
					/* of the contour                                          */
					ah_iup_interp( cur_touched + 1, end_point,
								   cur_touched, first_touched );

					/* if the first contour point isn't touched, interpolate */
					/* from the contour start to the first touched point     */
					if ( first_touched > points ) {
						ah_iup_interp( first_point, first_touched - 1,
									   cur_touched, first_touched );
					}
				}
			}
		}

		/* now save the interpolated values back to x/y */
		if ( dimension ) {
			for ( point = points; point < point_limit; point++ )
				point->y = point->u;

			touch_flag = ah_flah_touch_x;
			ah_setup_uv( outline, ah_uv_ox );
		} else
		{
			for ( point = points; point < point_limit; point++ )
				point->x = point->u;

			break; /* exit loop */
		}
	}
}

#endif /* !AH_OPTION_NO_WEAK_INTERPOLATION */


LOCAL_FUNC
void  ah_hinter_align_points( AH_Hinter*  hinter ) {
	ah_hinter_align_edge_points( hinter );

#ifndef AH_OPTION_NO_STRONG_INTERPOLATION
	ah_hinter_align_strong_points( hinter );
#endif

#ifndef AH_OPTION_NO_WEAK_INTERPOLATION
	ah_hinter_align_weak_points( hinter );
#endif
}


/*************************************************************************/
/*************************************************************************/
/*************************************************************************/
/****                                                                 ****/
/****       H I N T E R   O B J E C T   M E T H O D S                 ****/
/****                                                                 ****/
/*************************************************************************/
/*************************************************************************/
/*************************************************************************/


/* scale and fit the global metrics */
static
void  ah_hinter_scale_globals( AH_Hinter*  hinter,
							   FT_Fixed x_scale,
							   FT_Fixed y_scale ) {
	FT_Int n;
	AH_Face_Globals*  globals = hinter->globals;
	AH_Globals*       design = &globals->design;
	AH_Globals*       scaled = &globals->scaled;


	/* copy content */
	*scaled = *design;

	/* scale the standard widths & heights */
	for ( n = 0; n < design->num_widths; n++ )
		scaled->widths[n] = FT_MulFix( design->widths[n], x_scale );

	for ( n = 0; n < design->num_heights; n++ )
		scaled->heights[n] = FT_MulFix( design->heights[n], y_scale );

	/* scale the blue zones */
	for ( n = 0; n < ah_blue_max; n++ )
	{
		FT_Pos delta, delta2;


		delta = design->blue_shoots[n] - design->blue_refs[n];
		delta2 = delta;
		if ( delta < 0 ) {
			delta2 = -delta2;
		}
		delta2 = FT_MulFix( delta2, y_scale );

		if ( delta2 < 32 ) {
			delta2 = 0;
		} else if ( delta2 < 64 ) {
			delta2 = 32 + ( ( ( delta2 - 32 ) + 16 ) & - 32 );
		} else {
			delta2 = ( delta2 + 32 ) & - 64;
		}

		if ( delta < 0 ) {
			delta2 = -delta2;
		}

		scaled->blue_refs[n] =
			( FT_MulFix( design->blue_refs[n], y_scale ) + 32 ) & - 64;
		scaled->blue_shoots[n] = scaled->blue_refs[n] + delta2;
	}

	globals->x_scale = x_scale;
	globals->y_scale = y_scale;
}


static
void  ah_hinter_align( AH_Hinter*  hinter ) {
	ah_hinter_align_edge_points( hinter );
	ah_hinter_align_points( hinter );
}


/* finalize a hinter object */
void ah_hinter_done( AH_Hinter*  hinter ) {
	if ( hinter ) {
		FT_Memory memory = hinter->memory;


		ah_loader_done( hinter->loader );
		ah_outline_done( hinter->glyph );

		/* note: the `globals' pointer is _not_ owned by the hinter */
		/*       but by the current face object, we don't need to   */
		/*       release it                                         */
		hinter->globals = 0;
		hinter->face    = 0;

		FREE( hinter );
	}
}


/* create a new empty hinter object */
FT_Error  ah_hinter_new( FT_Library library,
						 AH_Hinter**  ahinter ) {
	AH_Hinter*  hinter = 0;
	FT_Memory memory = library->memory;
	FT_Error error;


	*ahinter = 0;

	/* allocate object */
	if ( ALLOC( hinter, sizeof( *hinter ) ) ) {
		goto Exit;
	}

	hinter->memory = memory;
	hinter->flags  = 0;

	/* allocate outline and loader */
	error = ah_outline_new( memory, &hinter->glyph )  ||
			ah_loader_new( memory, &hinter->loader ) ||
			ah_loader_create_extra( hinter->loader );
	if ( error ) {
		goto Exit;
	}

	*ahinter = hinter;

Exit:
	if ( error ) {
		ah_hinter_done( hinter );
	}

	return error;
}


/* create a face's autohint globals */
FT_Error  ah_hinter_new_face_globals( AH_Hinter*   hinter,
									  FT_Face face,
									  AH_Globals*  globals ) {
	FT_Error error;
	FT_Memory memory = hinter->memory;
	AH_Face_Globals*  face_globals;


	if ( ALLOC( face_globals, sizeof( *face_globals ) ) ) {
		goto Exit;
	}

	hinter->face    = face;
	hinter->globals = face_globals;

	if ( globals ) {
		face_globals->design = *globals;
	} else {
		ah_hinter_compute_globals( hinter );
	}

	face->autohint.data      = face_globals;
	face->autohint.finalizer = (FT_Generic_Finalizer)
							   ah_hinter_done_face_globals;
	face_globals->face       = face;

Exit:
	return error;
}


/* discard a face's autohint globals */
void  ah_hinter_done_face_globals( AH_Face_Globals*  globals ) {
	FT_Face face   = globals->face;
	FT_Memory memory = face->memory;


	FREE( globals );
}


static
FT_Error  ah_hinter_load( AH_Hinter*  hinter,
						  FT_UInt glyph_index,
						  FT_UInt load_flags,
						  FT_UInt depth ) {
	FT_Face face    = hinter->face;
	FT_GlyphSlot slot    = face->glyph;
	FT_Fixed x_scale = face->size->metrics.x_scale;
	FT_Fixed y_scale = face->size->metrics.y_scale;
	FT_Glyph_Metrics metrics;   /* temporary metrics */
	FT_Error error;
	AH_Outline*       outline = hinter->glyph;
	AH_Loader*        gloader = hinter->loader;
	FT_Bool no_horz_hints =
		( load_flags & AH_HINT_NO_HORZ_EDGES ) != 0;
	FT_Bool no_vert_hints =
		( load_flags & AH_HINT_NO_VERT_EDGES ) != 0;


	/* load the glyph */
	error = FT_Load_Glyph( face, glyph_index, load_flags );
	if ( error ) {
		goto Exit;
	}

	/* save current glyph metrics */
	metrics = slot->metrics;

	switch ( slot->format )
	{
	case ft_glyph_format_outline:
		/* first of all, copy the outline points in the loader's current  */
		/* extra points, which is used to keep original glyph coordinates */
		error = ah_loader_check_points( gloader, slot->outline.n_points + 2,
										slot->outline.n_contours );
		if ( error ) {
			goto Exit;
		}

		MEM_Copy( gloader->current.extra_points, slot->outline.points,
				  slot->outline.n_points * sizeof( FT_Vector ) );

		MEM_Copy( gloader->current.outline.contours, slot->outline.contours,
				  slot->outline.n_contours * sizeof( short ) );

		MEM_Copy( gloader->current.outline.tags, slot->outline.tags,
				  slot->outline.n_points * sizeof( char ) );

		gloader->current.outline.n_points   = slot->outline.n_points;
		gloader->current.outline.n_contours = slot->outline.n_contours;

		/* compute original phantom points */
		hinter->pp1.x = 0;
		hinter->pp1.y = 0;
		hinter->pp2.x = FT_MulFix( slot->metrics.horiAdvance, x_scale );
		hinter->pp2.y = 0;

		/* be sure to check for spacing glyphs */
		if ( slot->outline.n_points == 0 ) {
			goto Hint_Metrics;
		}

		/* now, load the slot image into the auto-outline, and run the */
		/* automatic hinting process                                   */
		error = ah_outline_load( outline, face ); /* XXX: change to slot */
		if ( error ) {
			goto Exit;
		}

		/* perform feature detection */
		ah_outline_detect_features( outline );

		if ( !no_horz_hints ) {
			ah_outline_compute_blue_edges( outline, hinter->globals );
			ah_outline_scale_blue_edges( outline, hinter->globals );
		}

		/* perform alignment control */
		ah_hinter_hint_edges( hinter, no_horz_hints, no_vert_hints );
		ah_hinter_align( hinter );

		/* now save the current outline into the loader's current table */
		ah_outline_save( outline, gloader );

		/* we now need to hint the metrics according to the change in */
		/* width/positioning that occured during the hinting process  */
		{
			FT_Pos old_width, new_width;
			FT_Pos old_advance, new_advance;
			FT_Pos old_lsb, new_lsb;
			AH_Edge*  edge1 = outline->vert_edges; /* leftmost edge  */
			AH_Edge*  edge2 = edge1 +
							  outline->num_vedges - 1; /* rightmost edge */


			old_width = edge2->opos - edge1->opos;
			new_width = edge2->pos  - edge1->pos;

			old_advance = hinter->pp2.x;
			old_lsb     = edge1->opos;
			new_lsb     = edge1->pos;

			new_advance = old_advance +
						  ( new_width + new_lsb - old_width - old_lsb );

			hinter->pp1.x = ( ( new_lsb - old_lsb ) + 32 ) & - 64;
			hinter->pp2.x = ( ( edge2->pos +
								( old_advance - edge2->opos ) ) + 32 ) & - 64;
		}

		/* good, we simply add the glyph to our loader's base */
		ah_loader_add( gloader );
		break;

	case ft_glyph_format_composite:
	{
		FT_UInt nn, num_subglyphs = slot->num_subglyphs;
		FT_UInt num_base_subgs, start_point, start_contour;
		FT_SubGlyph*  subglyph;


		start_point   = gloader->base.outline.n_points;
		start_contour = gloader->base.outline.n_contours;

		/* first of all, copy the subglyph descriptors in the glyph loader */
		error = ah_loader_check_subglyphs( gloader, num_subglyphs );
		if ( error ) {
			goto Exit;
		}

		MEM_Copy( gloader->current.subglyphs, slot->subglyphs,
				  num_subglyphs * sizeof( FT_SubGlyph ) );

		gloader->current.num_subglyphs = num_subglyphs;
		num_base_subgs = gloader->base.num_subglyphs;

		/* now, read each subglyph independently */
		for ( nn = 0; nn < num_subglyphs; nn++ )
		{
			FT_Vector pp1, pp2;
			FT_Pos x, y;
			FT_UInt num_points, num_new_points, num_base_points;


			/* gloader.current.subglyphs can change during glyph loading due */
			/* to re-allocation -- we must recompute the current subglyph on */
			/* each iteration                                                */
			subglyph = gloader->base.subglyphs + num_base_subgs + nn;

			pp1 = hinter->pp1;
			pp2 = hinter->pp2;

			num_base_points = gloader->base.outline.n_points;

			error = ah_hinter_load( hinter, subglyph->index,
									load_flags, depth + 1 );
			if ( error ) {
				goto Exit;
			}

			/* recompute subglyph pointer */
			subglyph = gloader->base.subglyphs + num_base_subgs + nn;

			if ( subglyph->flags & FT_SUBGLYPH_FLAG_USE_MY_METRICS ) {
				pp1 = hinter->pp1;
				pp2 = hinter->pp2;
			} else
			{
				hinter->pp1 = pp1;
				hinter->pp2 = pp2;
			}

			num_points     = gloader->base.outline.n_points;
			num_new_points = num_points - num_base_points;

			/* now perform the transform required for this subglyph */

			if ( subglyph->flags & ( FT_SUBGLYPH_FLAG_SCALE    |
									 FT_SUBGLYPH_FLAG_XY_SCALE |
									 FT_SUBGLYPH_FLAG_2X2      ) ) {
				FT_Vector*  cur   = gloader->base.outline.points +
									num_base_points;
				FT_Vector*  org   = gloader->base.extra_points +
									num_base_points;
				FT_Vector*  limit = cur + num_new_points;


				for ( ; cur < limit; cur++, org++ )
				{
					FT_Vector_Transform( cur, &subglyph->transform );
					FT_Vector_Transform( org, &subglyph->transform );
				}
			}

			/* apply offset */

			if ( !( subglyph->flags & FT_SUBGLYPH_FLAG_ARGS_ARE_XY_VALUES ) ) {
				FT_Int k = subglyph->arg1;
				FT_UInt l = subglyph->arg2;
				FT_Vector*  p1;
				FT_Vector*  p2;


				if ( start_point + k >= num_base_points          ||
					 l >= (FT_UInt)num_new_points  ) {
					error = FT_Err_Invalid_Composite;
					goto Exit;
				}

				l += num_base_points;

				/* for now, only use the current point coordinates     */
				/* we may consider another approach in the near future */
				p1 = gloader->base.outline.points + start_point + k;
				p2 = gloader->base.outline.points + start_point + l;

				x = p1->x - p2->x;
				y = p1->y - p2->y;
			} else
			{
				x = FT_MulFix( subglyph->arg1, x_scale );
				y = FT_MulFix( subglyph->arg2, y_scale );

				x = ( x + 32 ) & - 64;
				y = ( y + 32 ) & - 64;
			}

			{
				FT_Outline dummy = gloader->base.outline;


				dummy.points  += num_base_points;
				dummy.n_points = num_new_points;

				FT_Outline_Translate( &dummy, x, y );
			}
		}
	}
	break;

	default:
		/* we don't support other formats (yet?) */
		error = FT_Err_Unimplemented_Feature;
	}

Hint_Metrics:
	if ( depth == 0 ) {
		FT_BBox bbox;


		/* we must translate our final outline by -pp1.x, and compute */
		/* the new metrics                                            */
		if ( hinter->pp1.x ) {
			FT_Outline_Translate( &gloader->base.outline, -hinter->pp1.x, 0 );
		}

		FT_Outline_Get_CBox( &gloader->base.outline, &bbox );
		bbox.xMin &= -64;
		bbox.yMin &= -64;
		bbox.xMax  = ( bbox.xMax + 63 ) & - 64;
		bbox.yMax  = ( bbox.yMax + 63 ) & - 64;

		slot->metrics.width        = bbox.xMax - bbox.xMin;
		slot->metrics.height       = bbox.yMax - bbox.yMin;
		slot->metrics.horiBearingX = bbox.xMin;
		slot->metrics.horiBearingY = bbox.yMax;
		slot->metrics.horiAdvance  = hinter->pp2.x - hinter->pp1.x;
		/* XXX: TO DO - slot->linearHoriAdvance */

		/* now copy outline into glyph slot */
		ah_loader_rewind( slot->loader );
		error = ah_loader_copy_points( slot->loader, gloader );
		if ( error ) {
			goto Exit;
		}

		slot->outline = slot->loader->base.outline;
		slot->format  = ft_glyph_format_outline;
	}

Exit:
	return error;
}


/* load and hint a given glyph */
FT_Error  ah_hinter_load_glyph( AH_Hinter*    hinter,
								FT_GlyphSlot slot,
								FT_Size size,
								FT_UInt glyph_index,
								FT_Int load_flags ) {
	FT_Face face         = slot->face;
	FT_Error error;
	FT_Fixed x_scale      = size->metrics.x_scale;
	FT_Fixed y_scale      = size->metrics.y_scale;
	AH_Face_Globals*  face_globals = FACE_GLOBALS( face );


	/* first of all, we need to check that we're using the correct face and */
	/* global hints to load the glyph                                       */
	if ( hinter->face != face || hinter->globals != face_globals ) {
		hinter->face = face;
		if ( !face_globals ) {
			error = ah_hinter_new_face_globals( hinter, face, 0 );
			if ( error ) {
				goto Exit;
			}
		}
		hinter->globals = FACE_GLOBALS( face );
		face_globals    = FACE_GLOBALS( face );
	}

	/* now, we must check the current character pixel size to see if we */
	/* need to rescale the global metrics                               */
	if ( face_globals->x_scale != x_scale ||
		 face_globals->y_scale != y_scale ) {
		ah_hinter_scale_globals( hinter, x_scale, y_scale );
	}

	load_flags |= FT_LOAD_NO_SCALE | FT_LOAD_NO_RECURSE;

	ah_loader_rewind( hinter->loader );

	error = ah_hinter_load( hinter, glyph_index, load_flags, 0 );

Exit:
	return error;
}


/* retrieve a face's autohint globals for client applications */
void  ah_hinter_get_global_hints( AH_Hinter*  hinter,
								  FT_Face face,
								  void**      global_hints,
								  long*       global_len ) {
	AH_Globals*  globals = 0;
	FT_Memory memory  = hinter->memory;
	FT_Error error;


	/* allocate new master globals */
	if ( ALLOC( globals, sizeof( *globals ) ) ) {
		goto Fail;
	}

	/* compute face globals if needed */
	if ( !FACE_GLOBALS( face ) ) {
		error = ah_hinter_new_face_globals( hinter, face, 0 );
		if ( error ) {
			goto Fail;
		}
	}

	*globals      = FACE_GLOBALS( face )->design;
	*global_hints = globals;
	*global_len   = sizeof( *globals );

	return;

Fail:
	FREE( globals );

	*global_hints = 0;
	*global_len   = 0;
}


void  ah_hinter_done_global_hints( AH_Hinter*  hinter,
								   void*       global_hints ) {
	FT_Memory memory = hinter->memory;


	FREE( global_hints );
}


/* END */
