/* Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999 artofcode LLC.  All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version.

  This program 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
  General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.,
  59 Temple Place, Suite 330, Boston, MA, 02111-1307.

*/

/*$Id: gxhint3.c,v 1.3.2.1.2.1 2003/01/17 00:49:03 giles Exp $ */
/* Apply hints for Type 1 fonts. */
#include "gx.h"
#include "gserrors.h"
#include "gxarith.h"
#include "gxfixed.h"
#include "gxmatrix.h"
#include "gxfont.h"
#include "gxfont1.h"
#include "gxtype1.h"
#include "gzpath.h"

/* ------ Path hints ------ */

/* Forward references */
private void
     apply_hstem_hints(P3(gs_type1_state *, int, gs_fixed_point *)), apply_vstem_hints(P3(gs_type1_state *, int, gs_fixed_point *));


/*
 * Apply hints along a newly added tail of a subpath.
 * Path segments require hints as follows:
 *      Nearly vertical line: vstem hints at both ends.
 *      Nearly horizontal line: hstem hints at both ends.
 *      Curve with nearly vertical/horizontal start/end:
 *        vstem/hstem hints at start/end.
 * We also must take care to handle the implicit closing line for
 * subpaths that aren't explicitly closed.
 *
 * Note that "upper" and "lower" refer to device coordinates, which are
 * what we use throughout the Type 1 code; however, "horizontal" and
 * "vertical" refer to the character space coordinate system.
 */
#define HINT_VERT_LOWER 1
#define HINT_VERT_UPPER 2	/* must be > lower */
#define HINT_VERT (HINT_VERT_LOWER | HINT_VERT_UPPER)
#define HINT_HORZ_LOWER 4
#define HINT_HORZ_UPPER 8	/* must be > lower */
#define HINT_HORZ (HINT_HORZ_LOWER | HINT_HORZ_UPPER)
private inline bool
nearly_axial(fixed dmajor, fixed dminor)
{
    return (dminor <= dmajor >> 4);
}

/*
 * Determine which types of hints, if any, are applicable to a given
 * line segment.
 */
private int
line_hints(const gs_type1_state * pcis, const gs_fixed_point * p0,
	   const gs_fixed_point * p1)
{
    fixed dx = p1->x - p0->x;
    fixed dy = p1->y - p0->y;
    fixed adx, ady;
    bool xi = pcis->fh.x_inverted, yi = pcis->fh.y_inverted;
    int hints;

    /*
     * To figure out which side of the stem we are on, we assume that the
     * inside of the filled area is always to the left of the edge, i.e.,
     * edges moving in -X or +Y in character space are on the "upper" side
     * of the stem, while edges moving by +X or -Y are on the "lower" side.
     * (See section 3.5 of the Adobe Type 1 Font Format book.)
     */

    /*
     * Map the deltas back into character space.  This is essentially an
     * inverse-distance-transform with the combined matrix, but we don't
     * bother to undo the scaling, since it only matters for the axiality
     * test and we don't care about situations where X and Y scaling are
     * radically different.
     */
    if (xi)
	dx = -dx;
    if (yi)
	dy = -dy;
    if (pcis->fh.axes_swapped) {
	fixed t = dx;
	int ti = xi;

	dx = dy, xi = yi;
	dy = t, yi = ti;
    }
    adx = any_abs(dx);
    ady = any_abs(dy);
    /*
     * Note that since upper/lower refer to device space, we must
     * interchange them if the corresponding axis is inverted.
     */
    if (dy != 0 && nearly_axial(ady, adx)) {
	hints = (dy > 0 ? HINT_VERT_UPPER : HINT_VERT_LOWER);
	if (xi)
	    hints ^= (HINT_VERT_LOWER | HINT_VERT_UPPER);
    } else if (dx != 0 && nearly_axial(adx, ady)) {
	hints = (dx < 0 ? HINT_HORZ_UPPER : HINT_HORZ_LOWER);
	if (yi)
	    hints ^= (HINT_HORZ_LOWER | HINT_HORZ_UPPER);
    } else
	hints = 0;
    if_debug7('y', "[y]hint from 0x%lx(%1.4f,%1.4f) to 0x%lx(%1.4f,%1.4f) = %d\n",
	      (ulong) p0, fixed2float(p0->x), fixed2float(p0->y),
	      (ulong) p1, fixed2float(p1->x), fixed2float(p1->y),
	      hints);
    return hints;
}

/* Apply hints at a point.  Optionally return the amount of adjustment. */
private void
apply_hints_at(gs_type1_state * pcis, int hints, gs_fixed_point * ppt,
	       gs_fixed_point * pdiff)
{
    fixed px = ppt->x, py = ppt->y;

    if_debug4('y', "[y]applying hints %d to 0x%lx(%1.4f,%1.4f) ...\n",
	      hints, (ulong) ppt, fixed2float(px), fixed2float(py));
    if ((hints & HINT_VERT) != 0 &&
	(pcis->vstem_hints.count & pcis->dotsection_flag) != 0
	)
	apply_vstem_hints(pcis, (hints & HINT_VERT_UPPER) -
			  (hints & HINT_VERT_LOWER), ppt);
    if ((hints & HINT_HORZ) != 0 &&
	(pcis->hstem_hints.count & pcis->dotsection_flag) != 0
	)
	apply_hstem_hints(pcis, (hints & HINT_HORZ_UPPER) -
			  (hints & HINT_HORZ_LOWER), ppt);
    if (pdiff != NULL)
	pdiff->x = ppt->x - px,
	    pdiff->y = ppt->y - py;
    /* Here is where we would round *ppt to the nearest quarter-pixel */
    /* if we wanted to. */
    if_debug2('y', "[y] ... => (%1.4f,%1.4f)\n",
	      fixed2float(ppt->x), fixed2float(ppt->y));
}

/* Add a hint delta to a point. */
#ifndef DEBUG
inline
#endif
private void
add_hint_diff(gs_fixed_point * ppt, gs_fixed_point delta)
{
    if_debug7('y', "[y]adding diff (%1.4f,%1.4f) to 0x%lx(%1.4f,%1.4f) => (%1.4f,%1.4f)\n",
	      fixed2float(delta.x), fixed2float(delta.y), (ulong) ppt,
	      fixed2float(ppt->x), fixed2float(ppt->y),
	      fixed2float(ppt->x + delta.x), fixed2float(ppt->y + delta.y));
    ppt->x += delta.x;
    ppt->y += delta.y;
}

/* Test whether a line is null. */
inline private bool
line_is_null(gs_fixed_point p0, gs_fixed_point p1)
{
    return (any_abs(p1.x - p0.x) + any_abs(p1.y - p0.y) < fixed_epsilon * 4);
}

/*
 * Adjust the other control points of a curve proportionately when moving
 * one end.  The Boolean argument indicates whether the point being
 * adjusted is the one nearer the point that was moved.
 */
private fixed
scale_delta(fixed diff, fixed dv, fixed lv, bool nearer)
{
    if (dv == 0)
	return 0;
    /*
     * fixed_mult_quo requires non-negative 2nd and 3rd arguments,
     * and also 2nd argument < 3rd argument.
     * If it weren't for that, we would just use it directly.
     *
     * lv = 0 is implausible, but we have to allow for it.
     */
    if (lv == 0)
	return (nearer ? diff : fixed_0);
    if (lv < 0)
	lv = -lv, dv = -dv;
    if (dv < 0)
	dv = -dv, diff = -diff;
    /*
     * If dv > lv, there has been some kind of anomaly similar to
     * the lv = 0 case.
     */
    if (dv >= lv)
	return (nearer ? diff : fixed_0);
    else
	return fixed_mult_quo(diff, dv, lv);
}
private void
adjust_curve_start(curve_segment * pcseg, const gs_fixed_point * pdiff)
{
    fixed dx = pdiff->x, dy = pdiff->y;
    fixed end_x = pcseg->pt.x, end_y = pcseg->pt.y;
    const segment *prev = pcseg->prev;
    fixed lx = end_x - (prev->pt.x - dx), ly = end_y - (prev->pt.y - dy);
    gs_fixed_point delta;

    delta.x = scale_delta(dx, end_x - pcseg->p1.x, lx, true);
    delta.y = scale_delta(dy, end_y - pcseg->p1.y, ly, true);
    add_hint_diff(&pcseg->p1, delta);
    delta.x = scale_delta(dx, end_x - pcseg->p2.x, lx, false);
    delta.y = scale_delta(dy, end_y - pcseg->p2.y, ly, false);
    add_hint_diff(&pcseg->p2, delta);
}
private void
adjust_curve_end(curve_segment * pcseg, const gs_fixed_point * pdiff)
{
    fixed dx = pdiff->x, dy = pdiff->y;
    const segment *prev = pcseg->prev;
    fixed start_x = prev->pt.x, start_y = prev->pt.y;
    fixed lx = pcseg->pt.x - dx - start_x, ly = pcseg->pt.y - dy - start_y;
    gs_fixed_point delta;

    delta.x = scale_delta(dx, pcseg->p1.x - start_x, lx, false);
    delta.y = scale_delta(dy, pcseg->p1.y - start_y, ly, false);
    add_hint_diff(&pcseg->p1, delta);
    delta.x = scale_delta(dx, pcseg->p2.x - start_x, lx, true);
    delta.y = scale_delta(dy, pcseg->p2.y - start_y, ly, true);
    add_hint_diff(&pcseg->p2, delta);
}

/*
 * Propagate a final wraparound hint back through any null line segments
 * to a possible curve.  pseg_last.pt has already been adjusted.
 */
private void
apply_final_hint(segment * pseg_last, const gs_fixed_point * pdiff)
{
    segment *pseg;

    for (pseg = pseg_last;; pseg = pseg->prev) {
	segment *prev = pseg->prev;

	switch (pseg->type) {
	    case s_curve:
		adjust_curve_end(((curve_segment *) pseg), pdiff);
		return;
	    case s_line:
	    case s_line_close:
		if (!line_is_null(prev->pt, pseg->pt))
		    return;
		add_hint_diff(&prev->pt, *pdiff);
		break;
	    default:		/* s_start */
		return;
	}
    }
}

/*
 * Handle the end of the subpath wrapping around to the start.  This is
 * ugly, messy code that we should be able to improve, but I neither see how
 * to do it nor understand how the IBM Type 1 rasterizer can produce such
 * good results without doing anything like this.
 *
 * This is a separate procedure only for readability: it is only called
 * from one place in the next procedure.
 */
private void
apply_wrapped_hints(gs_type1_state * pcis, subpath * psub, segment * pseg,
		    int hints, gs_fixed_point * pdiff)
{
    /* Some fonts don't use closepath when they should.... */
    fixed ctemp;
    bool closed =
	(pseg->type == s_line_close ||
	 ((ctemp = pseg->pt.x - psub->pt.x,
	   any_abs(ctemp) < float2fixed(0.1)) &&
	  (ctemp = pseg->pt.y - psub->pt.y,
	   any_abs(ctemp) < float2fixed(0.1))));
    segment *const pfirst = psub->next;
    int hints_first = pcis->hints_initial;

    if (closed) {
	/*
	 * Apply the union of the hints at both the end (pseg) and the start
	 * (psub) of the subpath.  Note that we have already applied hints
	 * at the end, and hints_first at the start.  However, because of
	 * hint replacement, the points might differ even if hints ==
	 * hints_first.  In this case, the initial hints take priority,
	 * because the initial segment was laid down first.
	 */
	int do_x, do_y;
	gs_fixed_point diff2;

	if_debug2('y', "[y]closing closed, hints=%d, hints_first=%d\n",
		  hints, hints_first);
	if (pcis->fh.axes_swapped)
	    do_x = HINT_HORZ, do_y = HINT_VERT;
	else
	    do_x = HINT_VERT, do_y = HINT_HORZ;
	{
	    /* Apply hints_first - hints to the end. */
	    int hints_end = hints_first & ~hints;

	    diff2.x =
		(hints_end & do_x ?
		 psub->pt.x - pcis->unmoved_start.x : 0);
	    diff2.y =
		(hints_end & do_y ?
		 psub->pt.y - pcis->unmoved_start.y : 0);
	}
	{
	    /* Apply hints - hints_first to the start. */
	    int hints_start = hints & ~hints_first;

	    pdiff->x =
		(hints_start & do_x ?
		 pseg->pt.x - pcis->unmoved_end.x : 0);
	    pdiff->y =
		(hints_start & do_y ?
		 pseg->pt.y - pcis->unmoved_end.y : 0);
	}
	add_hint_diff(&pseg->pt, diff2);
	apply_final_hint(pseg, &diff2);
	add_hint_diff(&psub->pt, *pdiff);
	/*
	 * Now align the initial and final points, to deal with hint
	 * replacement.
	 */
	diff2.x = psub->pt.x - pseg->pt.x;
	diff2.y = psub->pt.y - pseg->pt.y;
	if (diff2.x || diff2.y) {
	    /* Force the points to coincide. */
	    pseg->pt = psub->pt;
	    apply_final_hint(pseg, &diff2);
	}
    } else {
	int hints_close =
	line_hints(pcis, &pcis->unmoved_end, &pcis->unmoved_start);

	hints_close &= ~(hints | hints_first);
	if_debug3('y', "[y]closing open, hints=%d, hints_close=%d, hints_first=%d\n",
		  hints, hints_close, hints_first);
	if (hints_close) {
	    apply_hints_at(pcis, hints_close, &pseg->pt, pdiff);
	    apply_final_hint(pseg, pdiff);
	    apply_hints_at(pcis, hints_close, &psub->pt, pdiff);
	} else
	    pdiff->x = pdiff->y = 0;
    }
    if (pfirst->type == s_curve)
	adjust_curve_start((curve_segment *) pfirst, pdiff);
}

/*
 * Apply hints along a subpath.  If closing is true, consider the subpath
 * closed; if not, we may add more to the subpath later.  In the latter case,
 * don't do anything if the subpath is closed, because we already applied
 * the hints.
 */
void
type1_apply_path_hints(gs_type1_state * pcis, bool closing, gx_path * ppath)
{
    segment *pseg = pcis->hint_next;
    segment *pnext;
    subpath *const psub = ppath->current_subpath;

    /*
     * hints holds the set of hints that have already been applied (if
     * applicable) to pseg->pt, and hence should not be applied again.
     */
    int hints = pcis->hints_pending;
    gs_fixed_point diff;

    /*
     * Since unknown OtherSubrs call apply_path_hints before returning
     * to the client, and since OtherSubrs may be invoked before the
     * [h]sbw is seen, it's possible that init_done < 0, i.e., the path
     * and hint members of the state haven't been set up yet.  In this
     * case, we know there are no relevant hints.
     */
    if (pcis->init_done < 0 || !(pcis->fh.use_x_hints | pcis->fh.use_y_hints))
	return;
    if (pseg == 0) {
	/* Start at the beginning of the subpath. */
	if (psub == 0)
	    return;
	if (psub->is_closed && !closing)
	    return;
	pseg = (segment *) psub;
	if (pseg->next == 0)
	    return;
	hints = 0;
	pcis->unmoved_start = psub->pt;
	pcis->unmoved_end = psub->pt;
    } else
	hints = pcis->hints_pending;
    diff.x = diff.y = 0;
    for (; (pnext = pseg->next) != 0; pseg = pnext) {
	int hints_next;

	/*
	 * Apply hints to the end of the previous segment (pseg)
	 * and the beginning of this one (pnext).
	 */
	gs_fixed_point dseg;

	switch (pnext->type) {
	    case s_curve:{
		    curve_segment *const pnext_curve = (curve_segment *) pnext;
		    int hints_first =
		    line_hints(pcis, &pcis->unmoved_end,
			       &pnext_curve->p1) & ~hints;
		    gs_fixed_point diff2;

		    if (pseg == (segment *) psub)
			pcis->hints_initial = hints_first;
		    if (hints_first)
			apply_hints_at(pcis, hints_first, &pseg->pt, &dseg);
		    else
			dseg.x = dseg.y = 0;
		    diff2.x = pseg->pt.x - pcis->unmoved_end.x;
		    diff2.y = pseg->pt.y - pcis->unmoved_end.y;
		    hints_next = line_hints(pcis, &pnext_curve->p2, &pnext->pt);
		    adjust_curve_start(pnext_curve, &diff2);
		    if (hints_next) {
			apply_hints_at(pcis, hints_next, &pnext_curve->p2, &diff);
			pcis->unmoved_end = pnext->pt;
			add_hint_diff(&pnext->pt, diff);
		    } else
			pcis->unmoved_end = pnext->pt;
		    break;
		}
	    case s_line_close:
		/* Undo any initial hints propagated to the end. */
		pnext->pt = pcis->unmoved_start;
	    default:		/* s_line, s_line_close */
		if (line_is_null(pnext->pt, pcis->unmoved_end)) {
		    /* This is a null line, just move it. */
		    hints_next = hints;
		    dseg.x = dseg.y = 0;	/* don't move p2 again */
		} else {
		    hints_next =
			line_hints(pcis, &pcis->unmoved_end, &pnext->pt);
		    if (hints_next & ~hints)
			apply_hints_at(pcis, hints_next & ~hints,
				       &pseg->pt, &dseg);
		    else
			dseg.x = dseg.y = 0;
		}
		if (pseg == (segment *) psub)
		    pcis->hints_initial = hints_next;
		pcis->unmoved_end = pnext->pt;
		if (hints_next)
		    apply_hints_at(pcis, hints_next, &pnext->pt, NULL);
	}
	if (pseg->type == s_curve)
	    adjust_curve_end((curve_segment *) pseg, &dseg);
	hints = hints_next;
    }
    if (closing) {
	apply_wrapped_hints(pcis, psub, pseg, hints, &diff);
	pcis->hint_next = 0;
	pcis->hints_pending = 0;
    } else {
	pcis->hint_next = pseg;
	pcis->hints_pending = hints;
    }
}

/* ------ Individual hints ------ */

private const stem_hint *search_hints(P2(stem_hint_table *, fixed));

/*
 * Adjust a point according to the relevant hints.
 * dx or dy is > 0 for the upper edge, < 0 for the lower.
 * The caller is responsible for checking use_hstem_hints or use_vstem_hints
 * and not calling the find_xxx_hints routine if this is false.
 * Note that if use_x/y_hints is false, no entries ever get made
 * in the stem hint tables, so these routines will not get called.
 */

private void
apply_vstem_hints(gs_type1_state * pcis, int dy, gs_fixed_point * ppt)
{
    fixed *pv = (pcis->fh.axes_swapped ? &ppt->y : &ppt->x);
    const stem_hint *ph = search_hints(&pcis->vstem_hints, *pv);

    if (ph != 0) {
	if_debug3('Y', "[Y]use vstem %d: %1.4f (%s)",
		  (int)(ph - &pcis->vstem_hints.data[0]),
		  fixed2float(*pv),
		  (dy == 0 ? "?!" : dy > 0 ? "upper" : "lower"));
#ifdef DEBUG
	if (dy == 0) {
	    lprintf("dy == 0 in apply_vstem_hints!\n");
	    return;
	}
#endif
	*pv += (dy > 0 ? ph->dv1 : ph->dv0);
	if_debug1('Y', " -> %1.4f\n", fixed2float(*pv));
    }
}

private void
apply_hstem_hints(gs_type1_state * pcis, int dx, gs_fixed_point * ppt)
{
    fixed *pv = (pcis->fh.axes_swapped ? &ppt->x : &ppt->y);
    const stem_hint *ph = search_hints(&pcis->hstem_hints, *pv);

    if (ph != 0) {
	if_debug3('Y', "[Y]use hstem %d: %1.4f (%s)",
		  (int)(ph - &pcis->hstem_hints.data[0]),
		  fixed2float(*pv),
		  (dx == 0 ? "?!" : dx > 0 ? "upper" : "lower"));
#ifdef DEBUG
	if (dx == 0) {
	    lprintf("dx == 0 in apply_vstem_hints!\n");
	    return;
	}
#endif
	*pv += (dx > 0 ? ph->dv1 : ph->dv0);
	if_debug1('Y', " -> %1.4f\n", fixed2float(*pv));
    }
}

/* Search one hint table for an adjustment. */
private const stem_hint *
search_hints(stem_hint_table * psht, fixed v)
{
    const stem_hint *table = &psht->data[0];
    const stem_hint *ph = table + psht->current;

    if (v >= ph->v0 && v <= ph->v1 && ph->active)
	return ph;
    /* We don't bother with binary or even up/down search, */
    /* because there won't be very many hints. */
    for (ph = &table[psht->count]; --ph >= table;)
	if (v >= ph->v0 && v <= ph->v1 && ph->active) {
	    psht->current = ph - table;
	    return ph;
	}
    return 0;
}
