/* Copyright (C) 1990, 1995, 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: gxhint2.c,v 1.2.6.1.2.1 2003/01/17 00:49:03 giles Exp $ */
/* Character level hints for Type 1 fonts. */
#include "memory_.h"
#include "gx.h"
#include "gserrors.h"
#include "gxarith.h"
#include "gxfixed.h"
#include "gxmatrix.h"
#include "gxfont.h"
#include "gxfont1.h"
#include "gxtype1.h"

/* Define the tolerance for testing whether a point is in a zone, */
/* in device pixels.  (Maybe this should be variable?) */
#define STEM_TOLERANCE float2fixed(0.05)

/* Forward references */

private stem_hint *type1_stem(P4(const gs_type1_state *, stem_hint_table *, fixed, fixed));
private fixed find_snap(P3(fixed, const stem_snap_table *, const pixel_scale *));
private alignment_zone *find_zone(P3(gs_type1_state *, fixed, fixed));

/* Reset the stem hints. */
void
reset_stem_hints(gs_type1_state * pcis)
{
    pcis->hstem_hints.count = pcis->hstem_hints.replaced_count = 0;
    pcis->vstem_hints.count = pcis->vstem_hints.replaced_count = 0;
    update_stem_hints(pcis);
}

/* Prepare to replace the stem hints. */
private void
save_replaced_hints(stem_hint_table * psht)
{
    int rep_count = min(psht->replaced_count + psht->count, max_stems);

    memmove(&psht->data[max_stems - rep_count], &psht->data[0],
	    psht->count * sizeof(psht->data[0]));
    psht->replaced_count = rep_count;
    psht->count = psht->current = 0;
}
void
type1_replace_stem_hints(gs_type1_state * pcis)
{
    if_debug2('y', "[y]saving hints: %d hstem, %d vstem\n",
	      pcis->hstem_hints.count, pcis->vstem_hints.count);
    save_replaced_hints(&pcis->hstem_hints);
    save_replaced_hints(&pcis->vstem_hints);
    if_debug2('y', "[y]total saved hints: %d hstem, %d vstem\n",
	      pcis->hstem_hints.replaced_count,
	      pcis->vstem_hints.replaced_count);
}

/* Update the internal stem hint pointers after moving or copying the state. */
void
update_stem_hints(gs_type1_state * pcis)
{
    pcis->hstem_hints.current = 0;
    pcis->vstem_hints.current = 0;
}

/* ------ Add hints ------ */

#undef c_fixed
#define c_fixed(d, c) m_fixed(d, c, pcis->fc, max_coeff_bits)

#define if_debug_print_add_stem(chr, msg, psht, psh, c, dc, v, dv)\
	if_debug10(chr, "%s %d/%d: %g,%g -> %g(%g)%g ; d = %g,%g\n",\
		   msg, (int)((psh) - &(psht)->data[0]), (psht)->count,\
		   fixed2float(c), fixed2float(dc),\
		   fixed2float(v), fixed2float(dv), fixed2float((v) + (dv)),\
		   fixed2float((psh)->dv0), fixed2float((psh)->dv1))

/* Compute and store the adjusted stem coordinates. */
private void
store_stem_deltas(const stem_hint_table * psht, stem_hint * psh,
		  const pixel_scale * psp, fixed v, fixed dv, fixed adj_dv)
{
    /*
     * We want to align the stem so its edges fall on pixel boundaries
     * (possibly "big pixel" boundaries if we are oversampling),
     * but if hint replacement has occurred, we must shift edges in a
     * consistent way.  This is a real nuisance, but I don't see how
     * to avoid it; if we don't do it, we get bizarre anomalies like
     * disappearing stems.
     */
    const stem_hint *psh0 = 0;
    const stem_hint *psh1 = 0;
    int i;

    /*
     * If we ever had a hint with the same edge(s), align this one
     * the same way.
     */
    for (i = max_stems - psht->replaced_count; i < max_stems; ++i) {
	const stem_hint *ph = &psht->data[i];

	if (ph == psh)
	    continue;
	if (ph->v0 == psh->v0)
	    psh0 = ph;
	if (ph->v1 == psh->v1)
	    psh1 = ph;
    }
    for (i = 0; i < psht->count; ++i) {
	const stem_hint *ph = &psht->data[i];

	if (ph == psh)
	    continue;
	if (ph->v0 == psh->v0)
	    psh0 = ph;
	if (ph->v1 == psh->v1)
	    psh1 = ph;
    }
    if (psh0 != 0) {
	psh->dv0 = psh0->dv0;
	if (psh1 != 0) {	/* Both edges are determined. */
	    psh->dv1 = psh1->dv1;
	} else {		/* Only the lower edge is determined. */
	    psh->dv1 = psh->dv0 + adj_dv - dv;
	}
    } else if (psh1 != 0) {	/* Only the upper edge is determined. */
	psh->dv1 = psh1->dv1;
	psh->dv0 = psh->dv1 + adj_dv - dv;
    } else {			/* Neither edge is determined. */
	fixed diff2_dv = arith_rshift_1(adj_dv - dv);
	fixed edge = v - diff2_dv;
	fixed diff_v = scaled_rounded(edge, psp) - edge;

	psh->dv0 = diff_v - diff2_dv;
	psh->dv1 = diff_v + diff2_dv;
    }
}

/*
 * The Type 1 font format uses negative stem widths to indicate edge hints.
 * We need to convert these into zero-width stem hints.
 */
private void
detect_edge_hint(fixed *xy, fixed *dxy)
{
    if (*dxy == -21) {
	/* Bottom edge hint. */
	*xy -= 21, *dxy = 0;
    } else if (*dxy == -20) {
	/* Top edge hint. */
	*dxy = 0;
    }
}

/* Add a horizontal stem hint. */
void
type1_do_hstem(gs_type1_state * pcis, fixed y, fixed dy,
	       const gs_matrix_fixed * pmat)
{
    stem_hint *psh;
    alignment_zone *pz;
    const pixel_scale *psp;
    fixed v, dv, adj_dv;
    fixed vtop, vbot;

    if (!pcis->fh.use_y_hints || !pmat->txy_fixed_valid)
	return;
    detect_edge_hint(&y, &dy);
    y += pcis->lsb.y + pcis->adxy.y;
    if (pcis->fh.axes_swapped) {
	psp = &pcis->scale.x;
	v = pcis->vs_offset.x + c_fixed(y, yx) + pmat->tx_fixed;
	dv = c_fixed(dy, yx);
    } else {
	psp = &pcis->scale.y;
	v = pcis->vs_offset.y + c_fixed(y, yy) + pmat->ty_fixed;
	dv = c_fixed(dy, yy);
    }
    if (dy < 0)
	vbot = v + dv, vtop = v;
    else
	vbot = v, vtop = v + dv;
    if (dv < 0)
	v += dv, dv = -dv;
    psh = type1_stem(pcis, &pcis->hstem_hints, v, dv);
    if (psh == 0)
	return;
    adj_dv = find_snap(dv, &pcis->fh.snap_h, psp);
    pz = find_zone(pcis, vbot, vtop);
    if (pz != 0) {		/* Use the alignment zone to align the outer stem edge. */
	int inverted =
	(pcis->fh.axes_swapped ? pcis->fh.x_inverted : pcis->fh.y_inverted);
	int adjust_v1 =
	(inverted ? !pz->is_top_zone : pz->is_top_zone);
	fixed flat_v = pz->flat;
	fixed overshoot =
	(pz->is_top_zone ? vtop - flat_v : flat_v - vbot);
	fixed pos_over =
	(inverted ? -overshoot : overshoot);
	fixed ddv = adj_dv - dv;
	fixed shift = scaled_rounded(flat_v, psp) - flat_v;

	if (pos_over > 0) {
	    if (pos_over < pcis->fh.blue_shift || pcis->fh.suppress_overshoot) {	/* Character is small, suppress overshoot. */
		if_debug0('y', "[y]suppress overshoot\n");
		if (pz->is_top_zone)
		    shift -= overshoot;
		else
		    shift += overshoot;
	    } else if (pos_over < psp->unit) {	/* Enforce overshoot. */
		if_debug0('y', "[y]enforce overshoot\n");
		if (overshoot < 0)
		    overshoot = -psp->unit - overshoot;
		else
		    overshoot = psp->unit - overshoot;
		if (pz->is_top_zone)
		    shift += overshoot;
		else
		    shift -= overshoot;
	    }
	}
	if (adjust_v1)
	    psh->dv1 = shift, psh->dv0 = shift - ddv;
	else
	    psh->dv0 = shift, psh->dv1 = shift + ddv;
	if_debug2('y', "[y]flat_v = %g, overshoot = %g for:\n",
		  fixed2float(flat_v), fixed2float(overshoot));
    } else {			/* Align the stem so its edges fall on pixel boundaries. */
	store_stem_deltas(&pcis->hstem_hints, psh, psp, v, dv, adj_dv);
    }
    if_debug_print_add_stem('y', "[y]hstem", &pcis->hstem_hints, psh,
			    y, dy, v, dv);
}

/* Add a vertical stem hint. */
void
type1_do_vstem(gs_type1_state * pcis, fixed x, fixed dx,
	       const gs_matrix_fixed * pmat)
{
    stem_hint *psh;
    const pixel_scale *psp;
    fixed v, dv, adj_dv;

    if (!pcis->fh.use_x_hints)
	return;
    detect_edge_hint(&x, &dx);
    x += pcis->lsb.x + pcis->adxy.x;
    if (pcis->fh.axes_swapped) {
	psp = &pcis->scale.y;
	v = pcis->vs_offset.y + c_fixed(x, xy) + pmat->ty_fixed;
	dv = c_fixed(dx, xy);
    } else {
	psp = &pcis->scale.x;
	v = pcis->vs_offset.x + c_fixed(x, xx) + pmat->tx_fixed;
	dv = c_fixed(dx, xx);
    }
    if (dv < 0)
	v += dv, dv = -dv;
    psh = type1_stem(pcis, &pcis->vstem_hints, v, dv);
    if (psh == 0)
	return;
    adj_dv = find_snap(dv, &pcis->fh.snap_v, psp);
    if (pcis->pfont->data.ForceBold && adj_dv < psp->unit)
	adj_dv = psp->unit;
    /* Align the stem so its edges fall on pixel boundaries. */
    store_stem_deltas(&pcis->vstem_hints, psh, psp, v, dv, adj_dv);
    if_debug_print_add_stem('y', "[y]vstem", &pcis->vstem_hints, psh,
			    x, dx, v, dv);
}

/* Adjust the character center for a vstem3. */
/****** NEEDS UPDATING FOR SCALE ******/
void
type1_do_center_vstem(gs_type1_state * pcis, fixed x0, fixed dx,
		      const gs_matrix_fixed * pmat)
{
    fixed x1 = x0 + dx;
    gs_fixed_point pt0, pt1, width;
    fixed center, int_width;
    fixed *psxy;

    if (gs_point_transform2fixed(pmat, fixed2float(x0), 0.0, &pt0) < 0 ||
	gs_point_transform2fixed(pmat, fixed2float(x1), 0.0, &pt1) < 0
	) {			/* Punt. */
	return;
    }
    width.x = pt0.x - pt1.x;
    if (width.x < 0)
	width.x = -width.x;
    width.y = pt0.y - pt1.y;
    if (width.y < 0)
	width.y = -width.y;
    if (width.y < float2fixed(0.05)) {	/* Vertical on device */
	center = arith_rshift_1(pt0.x + pt1.x);
	int_width = fixed_rounded(width.x);
	psxy = &pcis->vs_offset.x;
    } else {			/* Horizontal on device */
	center = arith_rshift_1(pt0.y + pt1.y);
	int_width = fixed_rounded(width.y);
	psxy = &pcis->vs_offset.y;
    }
    if (int_width == fixed_0 || (int_width & fixed_1) == 0) {	/* Odd width, center stem over pixel. */
	*psxy = fixed_floor(center) + fixed_half - center;
    } else {			/* Even width, center stem between pixels. */
	*psxy = fixed_rounded(center) - center;
    }
    /* We can't fix up the current point here, */
    /* but we can fix up everything else. */
/****** TO BE COMPLETED ******/
}

/* Add a stem hint, keeping the table sorted. */
/* We know that d >= 0. */
/* Return the stem hint pointer, or 0 if the table is full. */
private stem_hint *
type1_stem(const gs_type1_state * pcis, stem_hint_table * psht,
	   fixed v0, fixed d)
{
    stem_hint *bot = &psht->data[0];
    stem_hint *top = bot + psht->count;

    if (psht->count >= max_stems)
	return 0;
    while (top > bot && v0 < top[-1].v0) {
	*top = top[-1];
	top--;
    }
    /* Add a little fuzz for insideness testing. */
    top->v0 = v0 - STEM_TOLERANCE;
    top->v1 = v0 + d + STEM_TOLERANCE;
    top->index = pcis->hstem_hints.count + pcis->vstem_hints.count;
    top->active = true;
    psht->count++;
    return top;
}

/* Compute the adjusted width of a stem. */
/* The value returned is always a multiple of scale.unit. */
private fixed
find_snap(fixed dv, const stem_snap_table * psst, const pixel_scale * pps)
{				/* We aren't sure why a maximum difference of pps->half */
    /* works better than pps->unit, but it does. */
#define max_snap_distance (pps->half)
    fixed best = max_snap_distance;
    fixed adj_dv;
    int i;

    for (i = 0; i < psst->count; i++) {
	fixed diff = psst->data[i] - dv;

	if (any_abs(diff) < any_abs(best)) {
	    if_debug3('Y', "[Y]possibly snap %g to [%d]%g\n",
		      fixed2float(dv), i,
		      fixed2float(psst->data[i]));
	    best = diff;
	}
    }
    adj_dv = scaled_rounded((any_abs(best) < max_snap_distance ?
			     dv + best : dv),
			    pps);
    if (adj_dv == 0)
	adj_dv = pps->unit;
#ifdef DEBUG
    if (adj_dv == dv)
	if_debug1('Y', "[Y]no snap %g\n", fixed2float(dv));
    else
	if_debug2('Y', "[Y]snap %g to %g\n",
		  fixed2float(dv), fixed2float(adj_dv));
#endif
    return adj_dv;
#undef max_snap_distance
}

/* Find the applicable alignment zone for a stem, if any. */
/* vbot and vtop are the bottom and top of the stem, */
/* but without interchanging if the y axis is inverted. */
private alignment_zone *
find_zone(gs_type1_state * pcis, fixed vbot, fixed vtop)
{
    alignment_zone *pz;

    for (pz = &pcis->fh.a_zones[pcis->fh.a_zone_count];
	 --pz >= &pcis->fh.a_zones[0];
	) {
	fixed v = (pz->is_top_zone ? vtop : vbot);

	if (v >= pz->v0 && v <= pz->v1) {
	    if_debug2('Y', "[Y]stem crosses %s-zone %d\n",
		      (pz->is_top_zone ? "top" : "bottom"),
		      (int)(pz - &pcis->fh.a_zones[0]));
	    return pz;
	}
    }
    return 0;
}
