/*************************************************
*    The PMW Music Typesetter - 3rd incarnation  *
*************************************************/

/* Copyright (c) Philip Hazel, 1991 - 2008 */

/* Written by Philip Hazel, starting November 1991 */
/* This file last modified: October 2008 */


/* This file contains code for miscellanous functions used
for drawing slurs. */

#include "pmwhdr.h"
#include "outhdr.h"
#include "pagehdr.h"



/*************************************************
*         Set up start of slur processing        *
*************************************************/

/* This is called when processing [slur], both while setting up the cont
structure, and while actually outputting.

Argument:  the slur start item
Returns:   the slur processing structure that was set up
*/

slurstr *
misc_setstartslur(bstr *p)
{
b_slurstr *pp = (b_slurstr *)p;
slurstr *s = store_Xget(sizeof(slurstr));

s->slur = pp;
s->maxy = -BIGNUMBER;
s->miny =  BIGNUMBER;
s->gaps = NULL;
s->section = 1;

s->lastx = s->x = s->count = s->slopeleft = s->sloperight = 0;

/* Put in a default y value in case the slur doesn't cross anything visible */

s->lasty = s->y = ((pp->flags & sflag_b) == 0)? 16000 : 0;

/* If this was a crossing slur, we place it second on the stack, if possible.
Otherwise put it at th top. */

if ((pp->flags & sflag_x) != 0 && bar_cont->slurs != NULL)
  {
  s->next = (bar_cont->slurs)->next;
  (bar_cont->slurs)->next = s;
  }
else
  {
  s->next = bar_cont->slurs;
  bar_cont->slurs = s;
  }
return s;
}


/*************************************************
*        Find and unchain given slur             *
*************************************************/

/* This is called when processing [endslur], both while setting up the cont
structure, and while actually outputting. If [endslur] has an id, we seek that
slur, yielding NULL if not found; otherwise the first on the chain is yielded.

Argument:  the endslur item
Returns:   the found slur structure, removed from the chain of active slurs
*/

slurstr *
misc_getendslur(bstr *p)
{
slurstr *s = bar_cont->slurs;
int slurid = ((b_endslurstr *)p)->id;

if (slurid != 0)
  {
  slurstr **ss = &(bar_cont->slurs);
  BOOL found = FALSE;

  while (s != NULL)
    {
    if ((s->slur)->id == slurid)
      {
      found = TRUE;
      *ss = s->next;
      break;
      }
    ss = &(s->next);
    s = *ss;
    }

  if (!found) return NULL;
  }

else bar_cont->slurs = s->next;
return s;
}


/*************************************************
*         Find Bezier parameter fraction         *
*************************************************/

/* Given the coefficients of a Bezier curve, its x coordinate end points, and a
fraction, compute the value of t (0 <= t <= 1) for which the x coordinate on
the curve is the same as the x coordinate of the point that is the fraction of
the x-distance between the end points. This is used when drawing partial
curves.

The code in this function had to be massaged carefully to ensure that exactly 
the same result is obtained when run native and under valgrind, in order to 
make the tests run clean. The differences were only in the 3rd decimal place, 
insignificant in the actual output, but of course the comparisons failed.

Arguments:
  f          the fraction
  a, b, c    the Bezier coefficients
  x0, x1     the endpoint x coordinates

Returns:     the t value
*/

static double
bezfraction(double f, double a, double b, double c, double x0, double x1)
{
double wanted = x0 + (x1 - x0)*f;   /* The wanted x coordinate */
double start = 0.0;
double stop = 1.0;
double step = 0.1;

/* Outer repeat loop */

for (;;)
  {
  double t;

  /* Inner loop covers the current range */

  for (t = start; t < stop + step; t += step)
    {
    double x = ((a*t + b)*t + c)*t + x0;
    
    /* Make sure that -0.0 is actually 0.0; this can be different when running 
    valgrind. */
    
    if (fabs(x) < 0.000001) x = 0.0; 

    /* If stepped past the wanted point, set new bounds and a smaller step
    unless we are close enough. */

    if (x >= wanted)
      {
      if (fabs(x - wanted) < 10.0 || step < 0.001) return t;
      start = t - step;
      stop = t;
      step /= 10.0;
      break;
      }
    }

  /* If didn't reach it, return the right point */

  if (t >= stop + step) return stop;
  }

return f;    /* Should never be obeyed */
}


/*************************************************
*          Get coordinates of slur gap           *
*************************************************/

/* For a drawing function we need the coordinates of the end points and the
midpoint of a gap in the normal coordinate system. This is very tedious to
compute. The results are returned in a static vector. We have to repeat a lot
of the work needed for actually drawing the relevant portion of the slur.

Arguments:
  ix0, iy0   the start coordinates of the part-slur
  ix1, iy1   the end coordinates of the part slur
  flags      slur flags
  co         co parameter
  start      t-value for start of gap (fixed point)
  stop       t-value for end of gap (fixed point)

Returns:     vector of left, middle, right coordinates
*/

static int coords[6];

static int *
getgapcoords(int ix0, int iy0, int ix1, int iy1, int flags, int co, int start,
  int stop)
{
int above, wiggly;
int ox, oy;
double xx, yy, w, v;
double x0, x1, x2, x3, y0, y1, y2, y3;
double a, b, c, f, g;
double xxl, xxr, yyl, yyr, sa, ca;

/* Compute values needed for curved slur drawing. */

above  = ((flags & sflag_b) == 0)? (+1) : (-1);
wiggly = ((flags & sflag_w) == 0)? (+1) : (-1);

xx = (double)ix1 - (double)ix0;
yy = (double)iy1 - (double)iy0;
w = sqrt(xx*xx + yy*yy);

sa = yy/w;    /* sine */
ca = xx/w;    /* cosine */

w /= 2.0;
v = w*0.6667;

if (v > 10000.0) v = 10000.0;
co = (above * (co + ((xx > 20000)? 6000 : (int)(xx * 0.3))) * main_stavemagn)/1000;

f = ((double)start)/1000.0;
g = ((double)stop)/1000.0;

/* Calculate the origin of the coordinate system where the slur would be drawn.
We don't actually have to translate or rotate, since we are not actually going
to draw anything here. */

ox = (ix0+ix1+6*main_stavemagn)/2;
oy = (iy0+iy1)/2;

/* Set up traditional Bezier coordinates for the complete slur. */

x0 = -w;
x1 = v - w + (double)out_slurclx;
x2 = w - v + (double)out_slurcrx;
x3 = +w;

y0 = 0.05;
y1 = (double)(int)(co + out_slurcly);
y2 = (double)(int)(co*wiggly + out_slurcry);
y3 = 0.05;

/* Calculate the coefficients for the original x parametric equation. */

a = x3 - x0 + 3.0*(x1 - x2);
b = 3.0*(x2 - 2.0*x1 + x0);
c = 3.0*(x1 - x0);

/* The given fractions are fractions along the line joining the two end points.
These do not correspond linearly with the parameter t of the complete curve, so
we have to calculate new fractional values. */

if (f > 0.0 && f < 1.0) f = bezfraction(f, a, b, c, x0, x3);
if (g > 0.0 && g < 1.0) g = bezfraction(g, a, b, c, x0, x3);

/* Now calculate the new Bezier point coordinates for ends of the portion of
the slur that we want. */

xxl = x0 + ((a*f + b)*f + c)*f;
xxr = x0 + ((a*g + b)*g + c)*g;

/* Now do exactly the same for the y points */

a = y3 - y0 + 3.0*(y1 - y2);
b = 3.0*(y2 - 2.0*y1 + y0);
c = 3.0*(y1 - y0);

yyl = y0 + ((a*f + b)*f + c)*f;
yyr = y0 + ((a*g + b)*g + c)*g;

/* Now we have to get those coordinates back into the normal coordinate system.
First rotate, then remove the effect of the translation. */

coords[0] = (int)(xxl * ca - yyl * sa) + ox;
coords[1] = (int)(yyl * ca + xxl * sa) + oy;
coords[4] = (int)(xxr * ca - yyr * sa) + ox;
coords[5] = (int)(yyr * ca + xxr * sa) + oy;

/* Set up the mid point values and return the vector. */

coords[2] = (coords[0] + coords[4])/2;
coords[3] = (coords[1] + coords[5])/2;

return coords;
}


/*************************************************
*      Output text in a slur or line gap         *
*************************************************/

/*
Arguments:
  gt           the gap text structure
  x, y         coordinates of the middle of the gap
  num, den     slope parameters for the gap

Returns:       nothing
*/

static void
out_gaptext(gaptextstr *gt, int x, int y, int num, int den)
{
int *matrix;
int width, sn, cs;
int unscaled_fontsize = ((curmovt->fontsizes)->fontsize_text)[gt->size];
int fontsize = mac_muldiv(main_stavemagn, unscaled_fontsize, 1000);

matrix = ((curmovt->fontsizes)->fontmatrix_text)[gt->size];
if (matrix != NULL) memcpy(font_transform, matrix, 4*sizeof(int));

if (num != 0)    /* Not horizontal */
  {
  double hy = sqrt((double)num*(double)num + (double)den*(double)den);
  font_rotate((int)((45000.0*atan2((double)num, (double)den))/atan(1.0)));
  sn = (int)((1000.0*(double)num)/hy);
  cs = (int)((1000.0*(double)den)/hy);
  }
else
  {
  sn = 0;
  cs = 1000;
  den = 1;   /* To stop division by 0 below */
  }

width = string_width(gt->text, font_rm, fontsize);

x = x - width/2 +
  mac_muldiv(gt->x, cs, 1000) - mac_muldiv(gt->y - fontsize/4, sn, 1000);

y = y - (width*num)/(2*den) +
  mac_muldiv(gt->x, sn, 1000) + mac_muldiv(gt->y - fontsize/4, cs, 1000);

out_string(gt->text, font_rm, fontsize, x, out_ystave - y, gt->flags);
font_reset();
}


/*************************************************
*   Compute parameters & draw a slur or line     *
*************************************************/

/* This function is called when [endslur] is reached, or at the end of a line.

Arguments:
  s          the active slur structure
  x1         the x coordinate of the end of the slur
  npitch     pitch of the final note
  eol        TRUE if this slur goes to the end of the line

Returns:     nothing
*/

void
misc_drawslur(slurstr *s, int x1, int npitch, BOOL eol)
{
b_slurstr *ss = s->slur;
b_slurmodstr *sm = NULL;
b_slurmodstr *smm = ss->mods;

BOOL sol;
BOOL laststemup = out_laststemup[out_stave];

int adjustco = 0;
int slurflags = ss->flags;
int lineslur = (slurflags & sflag_l) != 0;
int below = (slurflags & sflag_b) != 0;
int absolute = (slurflags & sflag_abs) != 0;
int lay = (slurflags & sflag_lay) != 0;

int x0 = s->x;
int y0 = s->y;
int y1;

/* For continued slurs in style 0 we have to adjust the apparent end points and
then draw only part of the resulting slur. These are the start and end
fractional values. */

int dstart = 0;
int dstop = 1000;

/* Note: for absolute and {und,ov}erlay positioning, all this computation is
later over-ridden. We leave it in place so as not to disturb things for the x
values. */

/* End of line slurs take their vertical end from the max/min under them. Allow
for the fact that a line slur gets its end moved 3 pts "past" the note
position. Also, suppress wiggles for the first section - the other parts do the
suppression with the sol test and need to have the flag still set in order to
flip the above/below bit. Note that the final note has not yet been
incorporated into the max/min. */

if (eol)
  {
  if (below) y1 = (s->lasty < s->miny)? s->lasty : s->miny;
    else y1 = (s->lasty > s->maxy)? s->lasty : s->maxy;
  if (lineslur) x1 -= 3000;
  if (x0 != 0) slurflags &= ~sflag_w;   /* No wiggle */
  slurflags |= sflag_or;                /* Open on right if line */
  }
else    /* Not end of line slur */
  {
  y1 = (npitch == 0)? L_2L :
    misc_ybound(below, n_prevtie, TRUE, TRUE);

  /* If the last note was beamed, and the slur is on the same side as the beam,
  we need to put in an additional bit of space for clearance. Also, if the slur
  is on the opposite side to the stem, ditto. */

  if (below)
    {
    if (laststemup || out_lastnotebeamed) y1 -= 1000;
    }
  else
    {
    if (!laststemup || out_lastnotebeamed) y1 += 1000;
    }
  }

/* Set up left position at line start; if x1 is also zero it means we have hit
an [es] or [el] immediately after a bar line at a line break. This is not an
error; we just invent a small distance. Turn off the wiggle flag, but if it was
set, flip the above/below status. We must also do a vertical adjustment for the
final part of a split wiggly slur. */

if (x0 == 0)
  {
  sol = TRUE;
  slurflags |= sflag_ol;              /* Open on left if line */
  if ((slurflags & sflag_w) != 0)
    {
    slurflags &= ~sflag_w;            /* No wiggle */
    below = !below;                   /* Other direction */
    slurflags ^= sflag_b;             /* Must do this too */
    y1 = (npitch == 0)? L_2L :
      misc_ybound(below, n_prevtie, TRUE, TRUE);
    }

  x0 = out_sysblock->firstnoteposition + out_sysblock->xjustify - 10500;

  if (x1 == 0)
    {
    x1 = x0 + 10500;
    if (lineslur) x0 -= 10000;   /* lines normally start 10 pts right */
    }
  }
else sol = FALSE;

/* For wiggly slurs, move the final point to the other end of the last note. We
don't attempt any other fancy adjustments for these slurs. */

if ((slurflags & sflag_w) != 0)
  {
  y1 = (npitch == 0)? L_2L :
    misc_ybound(!below, n_prevtie, TRUE, TRUE);
  if (!below && !laststemup && (n_flags & nf_stem) != 0)
    x1 -= 5*main_stavemagn;
  }

/* For non-wiggly slurs, make adjustments according to the starting and ending
slopes if there are more than three notes in the slur. The "starting" slope
actually looks at more than the first note, but we aren't clever enough to do
the same at the end. The curvature is adjusted according to the max/min pitch
under the slur. */

else if (below)    /* slur below */
  {
  int miny = s->miny;

  if (!laststemup && (n_flags & nf_stem) != 0 && !lineslur && !eol)
    x1 -= 5*main_stavemagn;

  if (s->count > 3)
    {
    if (s->slopeleft < -400)
      y0 -= (s->slopeleft < -600)? 4000: 2000;
    if (s->sloperight > 400)
      y1 -= (s->sloperight > 600)? 4000: 2000;
    }

  if (miny < y0 && miny < y1)
    {
    int adjust = (y0 + y1)/2 - miny;
    if (lineslur)
      {
      if ((slurflags & sflag_h) != 0) y0 = y1 = miny;
        else { y0 -= adjust; y1 -= adjust; }
      }
    else adjustco += adjust;
    }
  }

else    /* slur above */
  {
  int maxy = s->maxy;
  if (s->count > 3)
    {
    if (s->slopeleft > 400)
      y0 += (s->slopeleft > 600)? 4000 : 2000;
    if (s->sloperight < -400)
      y1 += (s->sloperight < -600)? 4000 : 2000;
    }

  if (maxy > y0 && maxy > y1)
    {
    int adjust = maxy - (y0 + y1)/2;
    if (lineslur)
      {
      if ((slurflags & sflag_h) != 0) y0 = y1 = maxy;
        else { y0 += adjust; y1 += adjust; }
      }
    else adjustco += adjust;
    }
  }

/* Deal with the horizontal option (horizontal line slurs handled above, but
other cases not yet). */

if ((slurflags & sflag_h) != 0)
  {
  if ((!below && y1 > y0) || (below && y1 < y0)) y0 = y1;
    else y1 = y0;
  }

/* If this is a curved slur, arrange that the end points are not on staff
lines, and ensure that for longish slurs, the centre adjustment is at least 2.
Also do this for steep slurs, but not if absolute or at underlay level or if
the slur is short. */

if (!lineslur)
  {
  int ay0 = abs(y0);
  int ay1 = abs(y1);

  if (below)
    {
    if (y0 >= L_1S && (ay0 % 4000) < 500) y0 -= 1000;
    if (y1 >= L_1S && (ay1 % 4000) < 500) y1 -= 1000;
    }
  else
    {
    if (y0 <= L_5S && (ay0 % 4000) < 500) y0 += 1000;
    if (y1 <= L_5S && (ay1 % 4000) < 500) y1 += 1000;
    }

  if (x1 - x0 > 72000 && adjustco < 2000) adjustco = 2000;

  else if (!absolute && !lay && x1 - x0 > 24000)
    {
    int overall_slope = mac_muldiv(y1 - y0, 1000, x1 - x0);
    if (abs(overall_slope) > 500 && adjustco < 2000) adjustco = 2000;
    }
  }

/* If this is a line-type "slur", ensure that the default pitches (i_e. before
adding user movement) are above or below the staff, as necessary. */

else
  {
  if (below)
    {
    if (y0 > L_1L) y0 = L_1L;
    if (y1 > L_1L) y1 = L_1L;
    }
  else
    {
    if (y0 < L_6L) y0 = L_6L;
    if (y1 < L_6L) y1 = L_6L;
    }
  }

/* If the slur or line is marked as "absolute", then the vertical positions are
specified without reference to any intervening notes. We allow all the above to
happen, so that the correct x values get computed, and also this feature was
added later and it is easier not to disturb the above code. Ditto for
slurs/lines that are drawn at the underlay or overlay level. */

if (absolute)
  {
  y0 = y1 = below? 0 : 16000;
  }
else if (lay)
  {
  y0 = y1 = below?
    out_sysblock->ulevel[out_stave] : out_sysblock->olevel[out_stave];
  }

/* Finally, apply manual adjustments. All endpoints of all sections are
affected by the "ally" value. */

y0 += ss->ally;
y1 += ss->ally;

/* Most slurs appear on at most two lines; we need the slurmod structure for
sequence number zero for endpoint adjustments in all cases except for the
middle sections of a slur that exists on more than 2 lines, so get it in all
cases (if it exists). */

while (smm != NULL && smm->sequence != 0) smm = smm->next;

/* If this is part of a split slur, we need the slurmod structure that matches
this section, and in all cases its values are used. */

if (sol || eol)
  {
  sm = ss->mods;
  while (sm != NULL && sm->sequence != s->section) sm = sm->next;
  if (sm != NULL)
    {
    x0 += sm->lx;
    x1 += sm->rx;
    y0 += sm->ly;
    y1 += sm->ry;
    adjustco += sm->c;
    out_slurclx = (sm->clx * main_stavemagn)/1000;
    out_slurcly = (sm->cly * main_stavemagn)/1000;
    out_slurcrx = (sm->crx * main_stavemagn)/1000;
    out_slurcry = (sm->cry * main_stavemagn)/1000;
    }
  }

/* Other values depend on which bit of a split slur is being drawn. If it is
neither the starting section nor the ending section (that is, it's a whole line
of middle slur), use just the values from this section's data block. In other
words, there's no more to do. */

if (sol && eol)
  {
  }

/* The final portion of a split slur. Use values from its block, plus values
from the zero block for the right-hand end, which is also applied to the
vertical movement of the left-hand end. */

else if (sol)
  {
  if (smm != NULL)
    {
    x1 += smm->rx;
    y1 += smm->ry;
    y0 += smm->ry;
    }
  }

/* The first section of a split slur. Use values from its block plus values
from the zero block for the left-hand end, which is also applied to the
vertical movement of the right-hand end. */

else if (eol)
  {
  if (smm != NULL)
    {
    x0 += smm->lx;
    y0 += smm->ly;
    y1 += smm->ly;
    }
  }

/* An unsplit slur. Use values from the zero block. */

else if (smm != NULL)
  {
  x0 += smm->lx;
  x1 += smm->rx;
  y0 += smm->ly;
  y1 += smm->ry;
  adjustco += smm->c;
  out_slurclx = (smm->clx * main_stavemagn)/1000;
  out_slurcly = (smm->cly * main_stavemagn)/1000;
  out_slurcrx = (smm->crx * main_stavemagn)/1000;
  out_slurcry = (smm->cry * main_stavemagn)/1000;
  }

/* Need to correct for the jog length for absolute line slurs, so that the line
is at the height specified. Also need to move above slurs up. */

if (lineslur && (absolute || lay))
  {
  int x = adjustco + 3000;
  if (!below) x = -x;
  y0 += x;
  y1 += x;
  }

/* Adjust the verticals for the stave magnification */

y0 = mac_muldiv(y0, main_stavemagn, 1000);
y1 = mac_muldiv(y1, main_stavemagn, 1000);

/* Make adjustments and fudges for continued curved slurs if the style is not
the default. */

if (eol && !lineslur && curmovt->endlineslurstyle != 0)
  {
  x1 += x1 - x0;
  y1 += (y1 - y0)/5;
  adjustco += 2*(y1 - y0)/3;
  dstop = 500;
  }

if (sol && !lineslur && curmovt->endlineslurstyle != 0)
  {
  x0 -= x1 - x0;
  y0 -= (y1 - y0)/5;
  adjustco += 2*(y1 - y0)/3;
  dstart = 500;
  }

/* If this is a line slur, there may be a chain of gaps to be dealt with. Note
that the out_slur routine adds 7*main_stavemagn to the x1 value, to get it past
the final note. */

if (lineslur && s->gaps != NULL)
  {
  b_linegapstr *lg;
  gapstr *pg = s->gaps;
  int fudge = 7*main_stavemagn;

  /* First scan through and compute positions for any that are specified as a
  fraction of the way along the line. */

  while (pg != NULL)
    {
    lg = pg->gap;
    if (lg->hfraction >= 0)
      {
      pg->x = lg->xadjust + x0 +
        mac_muldiv(x1 + fudge - x0, lg->hfraction, 1000);
      }
    pg = pg->next;
    }

  /* The gaps may be in any order, horizontally. We could sort them before
  processing, but it is just as easy to pick out the leftmost and process it
  until there are none left. */

  while (s->gaps != NULL)
    {
    gapstr **gg = &(s->gaps);      /* Parent ptr */
    gapstr *g = *gg;               /* Active block ptr */
    int firstx = g->x;
    int xg0, yg0, num, den, xwidth;

    /* Scan to find the leftmost */

    pg = g;
    while (pg->next != NULL)
      {
      if ((pg->next)->x < firstx)
        {
        gg = &(pg->next);
        g = *gg;
        firstx = g->x;
        }
      pg = pg->next;
      }

    /* Draw the line to this gap, unless it has negative length. */

    num = y1 - y0;
    den = x1 + fudge - x0;
    lg = g->gap;

    if (num == 0) /* Optimize out the common case (horizontal) */
      {
      xwidth = lg->width;
      xg0 = g->x - xwidth/2;
      yg0 = y0;
      }
    else
      {
      double dnum = (double)num;
      double dden = (double)den;
      xwidth = (int)(((double)(lg->width) * dden) /
        sqrt(dnum*dnum + dden*dden));
      xg0 = g->x - xwidth/2;
      yg0 = y0 + mac_muldiv(xg0 - x0, num, den);
      }

    if (x0 < xg0)
      out_slur(x0, y0, xg0 - fudge, yg0, slurflags | sflag_or, adjustco, 0, 1000);

    /* Update the starting position for the next section. */

    x0 = xg0 + xwidth;
    y0 = yg0 + mac_muldiv(x0 - xg0, num, den);
    slurflags |= sflag_ol;

    /* If there is an associated draw function, set up the coordinates and call
    it. Note that lines are always drawn 3 points above or below the given y
    value, to leave space for the jog. */

    if (lg->draw != NULL)
      {
      draw_lgx = xwidth / 2;
      draw_lgy = (y0 - yg0)/2;
      draw_ox = xg0 + draw_lgx;
      draw_oy = yg0 + draw_lgy + (below? (-3000) : (3000));
      draw_gap = below? -1000 : +1000;
      out_dodraw(lg->draw, lg->args, FALSE);
      draw_lgx = draw_lgy = draw_gap = 0;
      }

    /* If there is an associated text string, arrange to print it centred
    in the gap. */

    if (lg->gaptext != NULL)
      { 
      out_gaptext(lg->gaptext, (xg0 + x0)/2,
        (y0 + yg0)/2 + (below? (-3000) : (3000)), num, den);
      }   

    /* Extract from the chain and free the block */

    *gg = g->next;
    store_free(g);
    }

  /* Now draw the rest of the line, if there is any left. */

  if (x0 < x1) out_slur(x0, y0, x1, y1, slurflags, adjustco, 0, 1000);
  }

/* The ability to have gaps in curved slurs was added at a later stage. Rather
than mess with the above code, put in separate code to handle it. The data is
the same as for line gaps. */

else if (s->gaps != NULL)
  {
  b_linegapstr *lg;
  gapstr *pg = s->gaps;
  int xlength = x1 + 7*main_stavemagn - x0;
  int start = dstart;
  int stop;

  /* First scan through and compute positions for any that are specified as a
  fraction of the way along the line. */

  while (pg != NULL)
    {
    lg = pg->gap;
    if (lg->hfraction >= 0)
      {
      pg->x = x0 + mac_muldiv(xlength, lg->hfraction, 1000) + lg->xadjust;
      }
    pg = pg->next;
    }

  /* The gaps may be in any order, horizontally. We could sort them before
  processing, but it is just as easy to pick out the left- most and process it
  until there are none left. */

  while (s->gaps != NULL)
    {
    gapstr **gg = &(s->gaps);      /* Parent ptr */
    gapstr *g = *gg;               /* Active block ptr */
    int firstx = g->x;

    /* Scan to find the leftmost */

    pg = g;
    while (pg->next != NULL)
      {
      if ((pg->next)->x < firstx)
        {
        gg = &(pg->next);
        g = *gg;
        firstx = g->x;
        }
      pg = pg->next;
      }

    /* Draw the slur to this gap, unless it has negative length. */

    lg = g->gap;
    stop = mac_muldiv(g->x - lg->width/2 - x0, 1000, xlength);

    if (stop > start)
      out_slur(x0, y0, x1, y1, slurflags, adjustco, start, stop);

    /* Compute start of next piece */

    start =  mac_muldiv(g->x + lg->width/2 - x0, 1000, xlength);

    /* If there is an associated draw function, set up the coordinates and call
    it. */

    if (lg->draw != NULL)
      {
      int *c = getgapcoords(x0, y0, x1, y1, slurflags, adjustco, stop, start);
      draw_ox = c[2];
      draw_oy = c[3];
      draw_lgx = c[2] - c[0];
      draw_lgy = c[3] - c[1];
      draw_gap = below? -1000 : +1000;
      out_dodraw(lg->draw, lg->args, FALSE);
      draw_lgx = draw_lgy = draw_gap = 0;
      }

    /* If there's associated text, output it. */

    if (lg->gaptext != NULL)
      {
      int *c = getgapcoords(x0, y0, x1, y1, slurflags, adjustco, stop, start);
      out_gaptext(lg->gaptext, c[2], c[3], c[5]-c[1], c[4]-c[0]);
      }

    /* Extract from the chain and free the block */

    *gg = g->next;
    store_free(g);
    }

  /* Now draw the rest of the slur, if there is any left. */

  if (start < 1000) out_slur(x0, y0, x1, y1, slurflags, adjustco, start, dstop);
  }

/* Else we have a slur or line with no gaps specified. Output it, provided it
has some positive horizontal length. If there isn't enough length, generate an
error and invent some suitable length. */

else
  {
  if (x0 >= x1)
    {
    error_moan(63, out_bar, out_stave);
    if (x0 == x1) x1 = x0 + 10000; else
      { int temp = x0; x0 = x1; x1 = temp; }
    }
  out_slur(x0, y0, x1, y1, slurflags, adjustco, dstart, dstop);
  }

/* Reset the globals that hold control point adjustments. */

out_slurclx = out_slurcly = out_slurcrx = out_slurcry = 0;

/* Free the slur block, which is now finished with. */

store_free(s);
}


/*************************************************
*      Draw slur or line from given values       *
*************************************************/

/* Originally there were only simple slurs, and these were drawn by the
ps_slur() function. When things got more complicated, additional work would
have had to be done in the PostScript header file. However, in the meanwhile,
the ps_path() function had been invented for drawing arbitrary shapes at the
logical (non-device) level. This function (out_slur()) is now called where
ps_slur() used to be called. In principle, it could do all the output. However,
to keep the size of PostScript down and for compatibility with the previous
PostScript, it still calls ps_slur() for PostScript output of complete,
non-dashed, curved slurs that can be handled by the old code.

New functionality is added in here, and in time I may remove the special
PostScript into here as well. Each change will cause the PostScript to change,
and hence the tests to fail to validate...

Note that as well as the parameters passed as arguments, there are also
parameter values in the global variables out_slurclx, out_slurcly, out_slurcrx,
and out_slurcry for corrections to the control points.

The t-values are the Bezier parameter values for drawing part slurs, given as
fixed point values between 0 and 1.0 respectively. For a whole slur, their int
values are therefore 0 and 1000.

Parts of the code in this function had to be massaged carefully to ensure that
exactly the same result is obtained when run native and under valgrind, in
order to make the tests run clean. The differences were only in the 3rd decimal
place, insignificant in the actual output, but of course the comparisons
failed.

Arguments:
  ix0, iy1     coordinates of the start of the slur
  ix1, iy1     coordinates of the end of the slur
  flag         the slur flags
  co           the co ("centre out") value
  start        the t-value at slur start (fixed point)  ) for drawing
  stop         the t-value at slur end (fixed point)    )   part-slurs

Returns:       nothing
*/

void
out_slur(int ix0, int iy0, int ix1, int iy1, int flags, int co, int start,
  int stop)
{
int x[10], y[10], cc[10];
int above, wiggly, ed_adjust;
double zz[4];
double xx, yy, w, v;
double x0, x1, x2, x3, y0, y1, y2, y3;
double ax, ay, bx, by, cx, cy, f, g;

if (ix1 == ix0 && iy1 == iy0) return;   /* Avoid crash */

/* Use ps_slur() to output complete, curved, non-dashed slurs to maintain
compatibility and smaller PostScript files. */

if (start == 0 && stop == 1000 && (flags & (sflag_l | sflag_i)) == 0)
  {
  ps_slur(ix0, iy0, ix1, iy1, flags, co);
  return;
  }

/* Compute values needed by both lines and curved slurs */

xx = (double)ix1 - (double)ix0;
yy = (double)iy1 - (double)iy0;

above  = ((flags & sflag_b) == 0)? (+1) : (-1);

/* Handle straight-line "slurs". For these, the start and stop values are not
used, as partial lines are drawn as separate whole lines with appropriate jog
flags. */

if ((flags & sflag_l) != 0)
  {
  int lineflags = 0;
  int adjust = 0;
  int thickness = (3*main_stavemagn)/10;
  ix1 += 7*main_stavemagn;
  co = mac_muldiv((co + 3000)*above, main_stavemagn, 1000);

  /* Convert the flags to the tie flags used by the ps_line function, then
  output the main portion of the line. Set the savedash flag if drawing a
  dotted line so that the jogs are drawn with the same dash settings. */

  if ((flags & sflag_i) != 0)
    lineflags |= ((flags & sflag_idot) == 0)?
      tief_dashed : tief_dotted | tief_savedash;
  if ((flags & sflag_e) != 0) lineflags |= tief_editorial;
  ps_line(ix0, iy0 + co, ix1, iy1 + co, thickness, lineflags);

  /* Don't pass any flag settings for drawing the jogs; for dotted lines the
  previous savedash ensures that the same setting is used for them. For dashed
  lines the jogs shouldn't be dashed. For dotted lines we may need to lengthen
  the jog to ensure at least one extra dot is drawn, and we change the
  thickness. Also, reduce the gap length slightly because there's an optical
  illusion that makes it look bigger than it is. Avoid redrawing the dot at the
  joining point. */

  if ((flags & sflag_idot) != 0)
    {
    thickness = main_stavemagn;
    ps_setdash(out_dashlength, (out_dashgaplength*95)/100, caj_round);
    if (abs(co) < 2*out_dashlength + out_dashgaplength)
      adjust = above*(2*out_dashlength + out_dashgaplength) - co;
    }
  else  co += (above*thickness)/2;

  if ((flags & sflag_ol) == 0)
    ps_line(ix0, iy0 + co - above*(out_dashlength+out_dashgaplength), ix0,
      iy0 - adjust, thickness, 0);
  if ((flags & sflag_or) == 0)
    ps_line(ix1, iy1 + co - above*(out_dashlength+out_dashgaplength), ix1,
      iy1 - adjust, thickness, 0);

  ps_setdash(0, 0, caj_butt);    /* Clear saved setting if no jogs */
  return;
  }

/* Compute values needed for curved slur drawing. */

wiggly = ((flags & sflag_w) == 0)? (+1) : (-1);
w = sqrt(xx*xx + yy*yy)/2.0;
v = w*0.6667;
if (v > 10000.0) v = 10000.0;

/* It is necessary to use floor() in the conversion of xx*0.3 to an integer in
the next statement in order to get the same value under valgrind. We know that
xx is positive, so we don't need to test whether to use floor() or ceil(). */

co = (above * (co + ((xx > 20000)?
  6000 : (int)(floor(xx * 0.3)))) * main_stavemagn)/1000;
  
f = ((double)start)/1000.0;
g = ((double)stop)/1000.0;

/* Preserve current coordinate system, translate and rotate so that the end
points of the slur lie on the x-axis, symetrically about the origin. For
ps_translate, the y value is relative to the stave base. Thereafter use
ps_abspath() for absolute values. */

ps_gsave();
ps_translate((ix0+ix1+6*main_stavemagn)/2, (iy0+iy1)/2);
ps_rotate(atan2(yy, xx));

/* Set up traditional Bezier coordinates for the complete slur. */

x0 = -w;
x1 = v - w + (double)out_slurclx;
x2 = w - v + (double)out_slurcrx;
x3 = +w;

y0 = 50.0;
y1 = (double)(int)(co + out_slurcly);
y2 = (double)(int)(co*wiggly + out_slurcry);
y3 = 50.0;

/* Calculate the coefficients for the original x parametric equation. */

ax = x3 - x0 + 3.0*(x1 - x2);
bx = 3.0*(x2 - 2.0*x1 + x0);
cx = 3.0*(x1 - x0);

/* The given fractions are fractions along the line joining the two end points.
These do not correspond linearly with the parameter t of the complete curve, so
we have to calculate new fractional values. */

if (f > 0.0 && f < 1.0) f = bezfraction(f, ax, bx, cx, x0, x3);
if (g > 0.0 && g < 1.0) g = bezfraction(g, ax, bx, cx, x0, x3);

/* Now calculate the new Bezier point coordinates for the portion of the slur
that we want, and set up the first path to be that portion. We used to compute 
the x values with just an (int) cast, but this gave slightly different values 
under valgrind. Using floor() or ceil() with a rounding value solves that 
problem. */

zz[0] = x0 + ((ax*f + bx)*f + cx)*f;
zz[1] = x0 + (((3.0*ax*g + bx)*f + 2.0*(bx*g + cx))*f + cx*g)/3.0;
zz[2] = x0 + (((3.0*ax*g + 2.0*bx)*g + cx)*f + 2.0*cx*g + bx*g*g)/3.0;
zz[3] = x0 + ((ax*g + bx)*g + cx)*g;

x[0] = (zz[0] >= 0.0)? (int)(floor(zz[0] + 0.0001)) : (int)(ceil(zz[0] - 0.0001));
x[1] = (zz[1] >= 0.0)? (int)(floor(zz[1] + 0.0001)) : (int)(ceil(zz[1] - 0.0001));
x[2] = (zz[2] >= 0.0)? (int)(floor(zz[2] + 0.0001)) : (int)(ceil(zz[2] - 0.0001));
x[3] = (zz[3] >= 0.0)? (int)(floor(zz[3] + 0.0001)) : (int)(ceil(zz[3] - 0.0001));

/* Now do exactly the same for the y points */

ay = y3 - y0 + 3.0*(y1 - y2);
by = 3.0*(y2 - 2.0*y1 + y0);
cy = 3.0*(y1 - y0);

zz[0] = y0 + ((ay*f + by)*f + cy)*f;
zz[1] = y0 + (((3.0*ay*g + by)*f + 2.0*(by*g + cy))*f + cy*g)/3.0;
zz[2] = y0 + (((3.0*ay*g + 2.0*by)*g + cy)*f + 2.0*cy*g + by*g*g)/3.0;
zz[3] = y0 + ((ay*g + by)*g + cy)*g;

y[0] = (zz[0] >= 0.0)? (int)(floor(zz[0] + 0.0001)) : (int)(ceil(zz[0] - 0.0001));
y[1] = (zz[1] >= 0.0)? (int)(floor(zz[1] + 0.0001)) : (int)(ceil(zz[1] - 0.0001));
y[2] = (zz[2] >= 0.0)? (int)(floor(zz[2] + 0.0001)) : (int)(ceil(zz[2] - 0.0001));
y[3] = (zz[3] >= 0.0)? (int)(floor(zz[3] + 0.0001)) : (int)(ceil(zz[3] - 0.0001));

cc[0] = path_move;
cc[1] = path_curve;

/* Deal with dashed slurs. The only way to do a decent job is to calculate the
actual length of the slur. This has to be done the hard way by numerically
integrating along the path, as the formulae don't give an analytic answer. To
make sure that a full dash ends the slur when there are gaps in the slur,
arrange for the slur to be drawn backwards if it doesn't start at the
beginning, but does end at the end. (We could compute separate dashes for the
individual parts, but that would probably look odd. Slurgaps in dashes slurs
should be most rare, anyway.) */

if ((flags & sflag_i) != 0)
  {
  int dashcount, dashlength, gaplength, thickness, length;
  double dlength = 0.0;
  double xp = x0;
  double yp = y0;
  double t;

  /* Compute the curve length by integration. */

  for (t = 0.0; t < 1.04; t += 0.05)
    {
    double xx = ((ax*t + bx)*t + cx)*t + x0;
    double yy = ((ay*t + by)*t + cy)*t + y0;
    dlength += sqrt((xx-xp)*(xx-xp) + (yy-yp)*(yy-yp));
    xp = xx;
    yp = yy;
    }
  length = (int)dlength;

  /* Choose a dash length, spacing parameter, and line thickness */

  if ((flags & sflag_idot) == 0)
    {
    dashlength = length/14;
    if (dashlength < 3000) dashlength = 3000;
    gaplength = (dashlength * 875)/1000;
    thickness = 500;
    }
  else
    {
    dashlength = 100;
    gaplength = ((length < 20000)? 3 : 4) * main_stavemagn;
    thickness = main_stavemagn;
    }

  /* Compute the number of dashes; if greater than one, compute the accurate
  gaplength and set dashing. */

  dashcount = (length + gaplength + (dashlength + gaplength)/2) /
    (dashlength + gaplength);
  if (dashcount > 1)
    {
    gaplength = (length - dashcount * dashlength)/(dashcount - 1);
    ps_setdash(dashlength, gaplength,
      ((flags & sflag_idot) == 0)? caj_butt : caj_round);
    }

  /* Invert drawing order of partial curve that ends at the full end */

  if (start > 0 && stop == 1000)
    {
    int tt;
    tt = x[0]; x[0] = x[3]; x[3] = tt;
    tt = x[1]; x[1] = x[2]; x[2] = tt;
    tt = y[0]; y[0] = y[3]; y[3] = tt;
    tt = y[1]; y[1] = y[2]; y[2] = tt;
    }

  /* Draw the dashed curve, and set editorial line adjustment to zero. */

  cc[2] = path_end;
  ps_abspath(x, y, cc, thickness);
  ps_setdash(0, 0, caj_butt);
  ed_adjust = 0;
  }

/* Deal with a non-dashed slur. For the other boundary of the slur, only the y
coordinates of the control points change. */

else
  {
  double aay, bby, ccy;
  ed_adjust = (9*main_stavemagn)/10;

  x[4] = x[3];
  x[5] = x[2];
  x[6] = x[1];
  x[7] = x[0];

  y0 = -50.0;
  y1 = (double)(int)(co + above*main_stavemagn + out_slurcly);
  y2 = (double)(int)(co*wiggly + above*main_stavemagn + out_slurcry);
  y3 = -50.0;

  aay = y3 - y0 + 3.0*(y1 - y2);
  bby = 3.0*(y2 - 2.0*y1 + y0);
  ccy = 3.0*(y1 - y0);

  y[7] = (int)(y0 + ((aay*f + bby)*f + ccy)*f);
  y[6] = (int)(y0 + (((3.0*aay*g + bby)*f + 2.0*(bby*g + ccy))*f + ccy*g)/3.0);
  y[5] = (int)(y0 + (((3.0*aay*g + 2.0*bby)*g + ccy)*f + 2.0*ccy*g + bby*g*g)/3.0);
  y[4] = (int)(y0 + ((aay*g + bby)*g + ccy)*g);

  cc[2] = path_line;
  cc[3] = path_curve;

  x[8] = x[0];
  y[8] = y[0];

  cc[4] = path_line;
  cc[5] = path_end;

  /* Fill the path (thickness = -1) */

  ps_abspath(x, y, cc, -1);
  }

/* Deal with editorial slurs - only draw the mark when drawing the middle
portion. */

if ((flags & sflag_e) != 0 && start < 500 && stop > 500)
  {
  int xx, yy;
  double dx, dy, theta, cs, sn;

  /* Calculate the midpoint of the curve from the parametric equations taking
  t = 0.5, and also calculate the slope at that point. */

  xx = (int)(((ax*0.5 + bx)*0.5 + cx)*0.5 - w);
  dx = (3.0*ax*0.5 + 2.0*bx)*0.5 + cx;

  yy = (int)(((ay*0.5 + by)*0.5 + cy)*0.5);
  dy = (3.0*ay*0.5 + 2.0*by)*0.5 + cy;

  /* Draw the editorial mark. Translate and rotate gave rounding errors, so do
  it by steam. */

  theta = atan2(dy, dx);
  cs = cos(theta);
  sn = sin(theta);

  if (above > 0)
    {
    x[0] = xx - (int)((2000.0+(double)ed_adjust)*sn);
    y[0] = yy + (int)((2000.0+(double)ed_adjust)*cs);
    x[1] = xx + (int)(2000.0*sn);
    y[1] = yy - (int)(2000.0*cs);
    }
  else
    {
    x[0] = xx - (int)(2000.0*sn);
    y[0] = yy + (int)(2000.0*cs);
    x[1] = xx + (int)((2000.0+(double)ed_adjust)*sn);
    y[1] = yy - (int)((2000.0+(double)ed_adjust)*cs);
    }

  cc[0] = path_move;
  cc[1] = path_line;
  cc[2] = path_end;
  ps_abspath(x, y, cc, 400);
  }

/* Restore the former coordinate system. */

ps_grestore();
}

/* End of setslur.c */
