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

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

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


/* This file contains code for miscellanous functions used when paginating,
displaying and printing */

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


/* Tables of accidental height/depths */

                         /* - ##    $     $$    %     # */
static int accdowntab[] = { 0, 0,    0,    0, 4000, 4000 };
static int accuptab[]   = { 0, 0, 4000, 4000, 4000, 4000 };


/* Tables used by the ybound function */

static int accboundtable[] = { 4000, 0, 0, 4000, 0, -4000, -4000, 0 };

static int resttable[] = {
  12000, 12000, 10000, 16000, 12000, 12000, 16000, 16000,
   8000, 10000,  8000,  2000,  4000,     0,     0, -4000 };



/*************************************************
*       Convert character value to UTF-8         *
*************************************************/

/* This function takes an integer value in the range 0 - 0x7fffffff
and encodes it as a UTF-8 character in 0 to 6 bytes.
                                                  
Arguments:                                        
  cvalue     the character value                  
  buffer     pointer to buffer for result - at least 6 bytes long
                                                   
Returns:     number of characters placed in the buffer
*/                        
                    
int
misc_ord2utf8(int cvalue, uschar *buffer)
{     
register int i, j;   
for (i = 0; i < 6; i++) if (cvalue <= utf8_table1[i]) break;
buffer += i;               
for (j = i; j > 0; j--)                                    
  {                  
  *buffer-- = 0x80 | (cvalue & 0x3f);                   
  cvalue >>= 6;
  }
*buffer = utf8_table2[i] | cvalue;
return i + 1;
}
                                       


/*************************************************
*          Return width of key signature         *
*************************************************/

/* This function checks for a special string for the key signature; otherwise
it finds the number of accidentals in a key signature, and returns an
appropriate printing width. In both cases this is the width for a 10-point 
stave (appropriate adjustment happens in the calling function when needed).

Arguments:   
  key       the key signature
  clef      the clef - used only to check for a special
    
Returns:    the printing width
*/

int 
misc_keywidth(int key, int clef)
{
pkeystr *pk;

for (pk = main_printkey; pk != NULL; pk = pk->next)
  { if (key == pk->key && clef == pk->clef) break; }
   
if (pk != NULL)
  {
  return string_width(pk->string, font_mf, 10000);
  }
   
else
  {       
  int n = main_keysigtable[key & 63];
  return abs(n) *
    (curmovt->accspacing)[(key>63)? ac_natural : (n>0)?ac_sharp : ac_flat];
  }   
}



/*************************************************
*          Return width of time signature        *
*************************************************/

/* This function sets up the appropriate strings for a time signature and then 
returns the width of the longer one.

Argument:    the time signature
Returns:     the printing width
*/

int 
misc_timewidth(int ts)
{
ptimestr *pt = main_printtime;
int *fontvector = (curmovt->fontsizes)->fontsize_text;
int offsetn, offsetd, yieldn, yieldd;
uschar vn[16];
uschar vd[16];
uschar *topstring = vn;
uschar *botstring = vd;

/* If not printing time signatures, return zero width */

if (!curmovt->showtime) return 0;

/* First see if this time signature has special strings specified for its
printing. */

while (pt != NULL)
  {
  if (pt->time == ts) break;
  pt = pt->next;
  }

/* If found special case, get strings and sizes from it */

if (pt != NULL)
  {
  offsetn = pt->offsettop;
  offsetd = pt->offsetbot;
  topstring = pt->top;
  botstring = pt->bot;
  }

/* Default printing for this time signature. First mask off the multiplier,
then check for the special cases of C and A. */

else
  {
  ts &= 0xFFFF;

  /* C and A have a known width */

  if (ts == time_common || ts == time_cut) return 7500;

  /* Non-special case - set up numerator and denominator, in the
  time signature font. */

  sprintf(CS vn, "%d", ts >> 8);
  sprintf(CS vd, "%d", ts & 255);

  offsetn = offsetd = ff_offset_ts;
  }

/* We now have in topstring and botstring two strings to print. The yield
is the greater of the two lengths, except when only the numerator
is to be printed. */

yieldn = string_width(topstring, font_bf, fontvector[offsetn]);
if (!curmovt->showtimebase) return yieldn;
yieldd = string_width(botstring, font_bf, fontvector[offsetd]);
return (yieldn > yieldd)? yieldn : yieldd;
}



/*************************************************
*          Find next note item in a bar          *
*************************************************/

/* If the second argument is not null, we use it to return any
ornament value. We always advance by at least one item. The initial
item need not be a note. Take care in case it is a Jump or End. 

Arguments:
  p         pointer to the current item
  orn       if not NULL, return the ornament that preceded the note
  
Returns:    pointer to the next note item or NULL if there isn't one 
*/

b_notestr *
misc_nextnote(b_notestr *p, int *orn)
{
int type = p->type;

if (type > b_Jump)   /* Advance only if not b_Jump or b_End */
  {
  p = (b_notestr *)((uschar *)p + length_table[p->type]);
  type = p->type;
  }

for (;;)
  {
  switch(type)
    {
    case b_End:                       /* In the special case of beaming over */
    if (!beam_overbeam) return NULL;  /* a barline, continue past the end. */
    break;                            /* There will always be another bar. */

    case b_note: 
    return p;

    case b_ornament:
    if (orn != NULL) *orn = ((b_ornamentstr *)p)->ornament;
    break;

    case b_Jump:
    p = (b_notestr *)(((b_Jumpstr *)p)->next);                      
    break;
    }

  p = (b_notestr *)((uschar *)p + length_table[type]);
  type = p->type;
  }

return p;  /* keep compiler happy - never obeyed */
}



/*************************************************
*      Make copy of continued data structure     *
*************************************************/

/* This is called at the start of outputting a system. It remembers the
parameters that are current at that point. At the start of a new system,
incslur is passed as TRUE, causing the section numbers of slurs to be
increased. (At the start of the first system, it is FALSE.) The result of the
function is a pointer to a vector of contstr structures, one for each stave.

Arguments:   
  p          pointer to the current contstr
  count      the highest stave number that we need to save (can be 0)
  incslur    when TRUE, increment the section number for each slur

Returns:     pointer to a vector of contstrs
*/

contstr *
misc_copycontstr(contstr *p, int count, BOOL incslur)
{
int i;
int len = (count+1)*sizeof(contstr);
contstr *yield = store_Xget(len);
contstr *q = yield;

/* Set up a copy of the vector */

memcpy(q, p, len);

/* For each member of the vector, copy the chained-on data blocks */

for (i = 0; i <= count; i++)
  {
  hairpinstr *h = p->hairpin;
  nbarstr *nb = p->nbar;
  nbarstr **nbprev = &(q->nbar);
  slurstr *s = p->slurs;
  slurstr **sprev = &(q->slurs);
  ulaystr *u = p->ulay;
  ulaystr **uprev = &(q->ulay);
  obeamstr *o = p->overbeam;

  /* Copy the hairpin structure */

  if (h != NULL)
    {
    hairpinstr *hh = store_Xget(sizeof(hairpinstr));
    hh[0] = h[0];
    q->hairpin = hh;
    }

  /* Copy the overbeam structure */

  if (o != NULL)
    {
    obeamstr *oo = store_Xget(sizeof(obeamstr));
    oo[0] = o[0];
    q->overbeam = oo;
    }

  /* Copy the nth time bar structures */

  while (nb != NULL)
    {
    nbarstr *nbb = store_Xget(sizeof(nbarstr));
    nbb[0] = nb[0];
    *nbprev = nbb;
    nbprev = &(nbb->next);
    nb = nb->next;
    }
  *nbprev = NULL;

  /* Copy the chain of slur structures. Each one may contain a sub-chain of gap
  structures. Increment the section number for each slur when requested - this
  happens when moving to a new system. */

  while (s != NULL)
    {
    slurstr *ss = store_Xget(sizeof(slurstr));
    gapstr *g = s->gaps;
    gapstr **gprev = &(ss->gaps);
    if (incslur) s->section += 1; 
    ss[0] = s[0];
 
    while (g != NULL)
      {
      gapstr *gg = store_Xget(sizeof(gapstr));
      gg[0] = g[0];
      *gprev = gg;
      gprev = &(gg->next);
      g = g->next;
      }
    *gprev = NULL;
    *sprev = ss;
    sprev = &(ss->next);
    s = s->next;
    }
  *sprev = NULL;

  /* Copy the chain of ulay structures */

  while (u != NULL)
    {
    ulaystr *uu = store_Xget(sizeof(ulaystr));
    uu[0] = u[0];
    *uprev = uu;
    uprev = &(uu->next);
    u = u->next;
    }
  *uprev = NULL;

  /* Advance to the next pair of vector entries */

  p++;
  q++;
  }

return yield;
}


/*************************************************
*         Free a continued data structure        *
*************************************************/

/* This is called at the end of outputting a system 

Arguments:
  p         pointer to the contstr vector
  count     number of staves saved
  
Returns:    nothing  
*/

void 
misc_freecontstr(contstr *p, int count)
{
int i;
contstr *q = p;

/* For each member of the vector, free the chained-on data blocks */

for (i = 0; i <= count; i++)
  {
  hairpinstr *h = q->hairpin;
  nbarstr *nb = q->nbar;
  slurstr  *s = q->slurs;
  ulaystr  *u = q->ulay;
  obeamstr *b = q->overbeam;

  if (h != NULL) store_free(h);
  if (b != NULL) store_free(b);

  while (nb != NULL)
    {
    nbarstr *nbb = nb->next;
    store_free(nb);
    nb = nbb;
    }

  while (s != NULL)
    {
    slurstr *ss = s->next;
    gapstr *g = s->gaps;
    while (g != NULL)
      {
      gapstr *gg = g->next;
      store_free(g);
      g = gg;
      }
    store_free(s);
    s = ss;
    }

  while (u != NULL)
    {
    ulaystr *uu = u->next;
    store_free(u);
    u = uu;
    }

  /* Advance to the next vector entry */

  q++;
  }

store_free(p);
}




/*************************************************
*          Common actions on cont data           *
*************************************************/

/* Certain items require the same actions when encountered in the advance scan
of each system by page_setcont() as when encountered while actually outputting
a system. These actions are handled in this function. 

Argument:    pointer to the item
Returns:     nothing
*/

void 
misc_commoncont(bstr *p)
{
switch(p->type)
  {

  /* Adjust bowing flags */

  case b_bowing:
  bar_cont->flags &= ~cf_bowingabove;
  if (((b_bowingstr *)p)->value) bar_cont->flags |= cf_bowingabove;
  break;

  /* Set note head style */

  case b_noteheads:
    {
    int style = ((b_noteheadsstr *)p)->value;
    if (style >= nh_only) bar_cont->flags |= cf_noteheads;
      else bar_cont->flags &= ~cf_noteheads;
    if (style != nh_only) bar_cont->noteheadstyle = style;
    }
  break;

  /* Switch notes on/off */

  case b_notes:
  bar_cont->flags &= ~cf_notes;
  if (((b_notesstr *)p)->value) bar_cont->flags |= cf_notes;
  break;

  /* Switch triplets on/off */

  case b_tripsw:
  bar_cont->flags &= ~cf_triplets;
  if (((b_tripswstr *)p)->value) bar_cont->flags |= cf_triplets;
  break;

  /* Set slope for next beam */

  case b_slope:
  beam_forceslope = ((b_slopestr *)p)->value;
  break;
  }
}



/*************************************************
*    See if there is pending {und,ov}erlay       *
*************************************************/

/* These functions are called by page_setcont() below.

Argument:   nothing
Returns:    TRUE if there is pending underlay
*/

static BOOL 
pendulay(void)
{
ulaystr *u = bar_cont->ulay;
while (u != NULL)
  {
  if (u->level < olay_offset) return TRUE;
  u = u->next;
  }
return FALSE;
}


/* 
Argument:   nothing
Returns:    TRUE if there is pending overlay
*/

static BOOL 
pendolay(void)
{
ulaystr *u = bar_cont->ulay;
while (u != NULL)
  {
  if (u->level >= olay_offset) return TRUE;
  u = u->next;
  }
return FALSE;
}


/*************************************************
*        Shorten stem of note if appropriate     *
*************************************************/

/* This function is called from page_setcont(). We have to flag the note and
not do it twice, to cope with repeated bars. 

Arguments:
  p            the note
  upflag       TRUE if the stem is up
  
Returns:       nothing  
*/

static void 
shorten_stem(b_notestr *tp, int upflag)
{
int shorten = 0;

if (upflag)
  {
  if (tp->spitch > 136) shorten = 250*(tp->spitch - 136);
  }
else
  {
  if (tp->spitch < 136) shorten = 250*(136 - tp->spitch);
  }
  
if (shorten != 0 && (tp->flags & nf_shortened) == 0)
  { 
  tp->yextra -= (shorten <= curmovt->shorten)? shorten : curmovt->shorten;
  tp->flags |= nf_shortened; 
  } 
}   



/*************************************************
*      Advance cont data to end of system        *
*************************************************/

/* This is called when paginating, when the end of a system has been chosen.
The working cont structure is in page_cont. A "snapshot" copy has been taken
which represents the state at the start of the system. We must now advance
through the system to get ready for the next snapshot. The system we are
dealing with is in page_sysblock.

This pass also handles changes of stave and system spacing, setting up new data
blocks as necessary. It also handles changes of page number.

While we are doing this, we can compute the tiecount values to go in the tie
control blocks. We cannot do this when reading in, because we don't know the
stem direction at the time the tie block is created. The count value is the
number of ties that take the opposite stemflag to the note.

We also have to do a lot of work if we encounter a beamed-over bar line at the
end of a system.

Arguments:  none
Returns:    nothing
*/

void 
page_setcont(void)
{
BOOL hadssnext = FALSE;
BOOL hadsshere = FALSE;
int bar = 0;
int stave;

for (stave = 0; stave <= page_lastwanted; stave++)
  {
  if (mac_teststave(curmovt->staves, stave))
    {
    BOOL no_ulay, no_olay;
    bar_cont = page_cont + stave;
    no_ulay = !pendulay();
    no_olay = !pendolay();

    for (bar = page_sysblock->barstart; bar <= page_sysblock->barend; bar++)
      {
      bstr *p = ((curmovt->stavetable)[stave])->barindex[bar];

      if (p != NULL && p->type != b_End)
        {
        int moff = 0;
        int beammoff = 0;
        int lastAlevel = 0;       /* Above level */
        int lastBlevel = 0;       /* Below level */
        int type = p->type;
        int chordcount = 0;
        BOOL hadulay = FALSE;
        BOOL hadolay = FALSE;
        BOOL upflag = FALSE;
        b_notestr *beamfirst = NULL;

        beam_forceslope = BIGNUMBER;

        for (;;)
          {
          switch(type)
            {
            case b_Jump:
            p = (bstr *)(((b_Jumpstr *)p)->next);                      
            break;

            case b_End:
            if (((b_Endstr *)p)->overbeam && bar == page_sysblock->barend &&
                beamfirst != NULL)

            /* We have a beam that may be carried over the end of a bar. We
            have to fudge up various values that will be set when the beam is
            drawn, in order to run setupbeam() so that we can find the beam's
            slope. This is preserved in the cont structure for use at the start
            of the system. */

              {
              barposstr *bp = curmovt->posvector + bar;
              out_postable = out_posptr = bp->vector;
              out_poslast = out_postable + bp->count - 1;
              out_bar = bar;
              out_stave = stave;
              out_gracenotes = FALSE;
              out_grace_fudge = 0;
              out_sysblock = page_sysblock;
              beam_offsetadjust = 0;
              n_upflag = (beamfirst->flags & nf_stemup) != 0;
              n_upfactor = n_upflag? (+1):(-1);
              (void)out_setupbeam(beamfirst, beammoff, TRUE, FALSE);
              }
            break;
            
            case b_footnote:
              {
              headstr *h = &(((b_footnotestr *)p)->h);
              headstr *hh = h;
              headstr *lh = NULL; 
              page_justifyheading(h);
              while (hh != NULL) 
                {
                lh = hh; 
                page_newfootnotedepth += hh->space;
                hh = hh->next;
                }  
              if (page_newfootnotes == NULL) page_newfootnotes = h; else
                { 
                page_lastnewfootnote->next = h;
                h->b.spaceabove = curmovt->footnotesep;
                }  
              page_lastnewfootnote = lh;
              }
            break;    

            case b_slur:
            (void)misc_setstartslur(p);
            break;

            case b_endslur:
              {
              slurstr *s = misc_getendslur(p);
              if (s == NULL)
                error_moan(62, ((b_endslurstr *)p)->id, bar, stave);
              else store_free(s);
              }
            break;

            case b_tie:
              {
              b_tiestr *pp = (b_tiestr *)p;
              bar_cont->tie = pp;
              if (pp->abovecount == 0 && pp->belowcount == 0)
                {
                if (upflag)
                  pp->belowcount = (chordcount > 1)? chordcount/2 : 1;
                else
                  pp->abovecount = (chordcount+1)/2;
                }

              if (pp->abovecount == 0 && pp->belowcount < chordcount)
                pp->abovecount = chordcount - pp->belowcount;

              else if (pp->belowcount == 0 && pp->abovecount < chordcount)
                pp->belowcount = chordcount - pp->abovecount;

              /* Allow for tie below on underlay level*/

              if (lastBlevel != 0 && pp->belowcount > 0)
                {
                lastBlevel -= 3500;
                if (page_sysblock->ulevel[stave] > lastBlevel)
                  page_sysblock->ulevel[stave] = lastBlevel;
                }

              /* Allow for tie above on overlay level*/

              if (lastAlevel != 0 && pp->abovecount > 0)
                {
                lastAlevel += 3500;
                if (page_sysblock->olevel[stave] < lastAlevel)
                  page_sysblock->olevel[stave] = lastAlevel;
                }
              }
            break;

            /* For notes/chords we calculate the {und,ov}erlay pitch, and also
            set flags for a possible subsequent tie. Skip rests, of course, and
            also skip the underlay calculation for notes that neither have
            their own text nor have an extender or hyphens under them
            (indicated by the existence of ulaystr data). Keep track of the
            last start-of-beam note, in case we are in an end-of-line bar with
            a continued beam. Handle automatic stem length adjustment. */

            case b_note:
            bar_cont->tie = NULL;
            if (((b_notestr *)p)->spitch != 0)
              {
              b_notestr *tp = (b_notestr *)p;
              int flags = tp->flags;
              int acflags = tp->acflags; 
              int apitch = tp->spitch;
              int bpitch = tp->spitch;
              int acc = tp->acc;
              int stemlength;

              if (tp->notetype >= quaver)
                {
                if (beamfirst == NULL)
                  {
                  beamfirst = tp;
                  beammoff = moff;
                  }
                }
              else
                {
                beamfirst = NULL;
                beam_forceslope = BIGNUMBER;
                }

              upflag = (flags & nf_stemup) != 0;
              if (curmovt->shorten && tp->length != 0) 
                shorten_stem(tp, upflag);
               
              stemlength = tp->yextra; 
              chordcount = 1;
              mac_advancechord(tp);

              while (tp->type == b_chord)
                {
                chordcount++;
                flags |= tp->flags;
                acflags |= tp->acflags; 
                if (tp->spitch > apitch)
                  {
                  apitch = tp->spitch;
                  acc = tp->acc;
                  }
                if (tp->spitch < bpitch)
                  {
                  bpitch = tp->spitch;
                  acc = tp->acc;
                  }
                if (curmovt->shorten && tp->length != 0) 
                  shorten_stem(tp, upflag);
                if (abs(tp->yextra) > abs(stemlength)) stemlength = tp->yextra;
                mac_advancechord(tp);
                }

              /* Now do the underlay/overlay stuff */

              lastAlevel = (apitch - 124)*1000 +
                ((!upflag || (flags & nf_stem) == 0)?
                   (5*accuptab[acc])/4 : (14000 + stemlength));

              lastBlevel = (bpitch - 129)*1000 -
                ((hadulay || no_ulay)? page_ulaysize : 4000) -
                  ((upflag || (flags & nf_stem) == 0)?
                     accdowntab[acc]/2 : (13000 + stemlength));

              /* Allow for dynamics */

              if ((acflags & af_dynamics) != 0)
                {
                int dynextraA = accboundtable[
                  (((acflags & af_opposite) == 0)? 0:1) + (upflag? 2:0)];
                int dynextraB = accboundtable[4 +
                  (((acflags & af_opposite) == 0)? 0:1) + (upflag? 2:0)];

                lastAlevel += dynextraA;
                lastBlevel += dynextraB;

                /* That's all if no relevant accent, or the accent falls
                inside the staff; otherwise make sure the level is
                suitably outside the staff. */

                if (dynextraA != 0 && (acflags & af_dynoutside) != 0)
                  lastAlevel += dynextraA;   /* these are bigger accents */
                if (dynextraB != 0 && (acflags & af_dynoutside) != 0)
                  lastBlevel += dynextraB;   /* these are bigger accents */
                }

              /* Impose {min,max}imum level and keep {high,low}est
              level for the line if appropriate */

              if (lastAlevel < 20000) lastAlevel = 20000;

              if (lastBlevel > -page_ulaysize - 1000)
                lastBlevel = -page_ulaysize - 1000;

              if (no_olay || hadolay || pendolay())
                {
                if (page_sysblock->olevel[stave] < lastAlevel)
                  page_sysblock->olevel[stave] = lastAlevel;
                }

              if (no_ulay || hadulay || pendulay())
                {
                if (page_sysblock->ulevel[stave] > lastBlevel)
                  page_sysblock->ulevel[stave] = lastBlevel;
                }

              /* Turn off value if don't want tie noticed */

              if (upflag && (flags & nf_stem) != 0) lastAlevel = 0;
              if (!upflag && (flags & nf_stem) != 0) lastBlevel = 0;
              }

            /* Deal with rests - kill any outstanding underlay block for
            extensions, but not for hyphens. */

            else
              {
              ulaystr **uu = &(bar_cont->ulay);
              ulaystr *u = *uu;
              while (u != NULL)
                {
                if (u->type == '=')
                  {
                  *uu = u->next;
                  store_free(u);
                  }
                else uu = &(u->next);
                u = *uu;
                }
 
              lastAlevel = lastBlevel = 0;
              if (((b_notestr *)p)->notetype < quaver) beamfirst = NULL;
              }
              
            /* Notes and rests dealt with */ 

            moff += ((b_notestr *)p)->length;
            hadulay = hadolay = FALSE;
            break;

            /* Deal with beam breaks */

            case b_beambreak:
            beamfirst = NULL;
            beam_forceslope = BIGNUMBER;
            break;

            /* Deal with resets */

            case b_reset:
            moff = 0;
            break;

            /* Create or delete hairpin pending blocks */

            case b_hairpin:
              {
              b_hairpinstr *h = (b_hairpinstr *)p;
              if (h->opt == 0)
                {
                if (bar_cont->hairpin != NULL)
                  {
                  store_free(bar_cont->hairpin);
                  bar_cont->hairpin = NULL;
                  }
                }
              else out_setstarthairpin(h, 0);
              }
            break;

            /* For nth time bars we need only keep one block, since continued
            cases won't be printing the numbers. */

            case b_nbar:
            if (bar_cont->nbar == NULL)
              {
              b_nbarstr *b = (b_nbarstr *)p;
              out_setstartnbar(b, 0, 0);
              }
            break;

            case b_all:
            if (bar_cont->nbar != NULL)
              {
              store_free(bar_cont->nbar);
              bar_cont->nbar = NULL;
              }
            break;

            case b_setclef:
            bar_cont->clef = ((b_setclefstr *)p)->value;
            break;

            case b_clef:
            bar_cont->clef = ((b_clefstr *)p)->trueclef;
            break;

            case b_settime:
            bar_cont->time = ((b_settimestr *)p)->value;
            break;

            case b_time:
            bar_cont->time = ((b_timestr *)p)->time;
            break;

            case b_setkey:
            bar_cont->key = ((b_setkeystr *)p)->value;
            break;

            case b_key:
            bar_cont->key = ((b_keystr *)p)->key;
            break;


            case b_sghere:            /* immediate change of system gap */
              {
              b_sgstr *pp = (b_sgstr *)p;
              int v = pp->value;
              if (pp->opt == '+') page_sysblock->systemgap += v;
                else page_sysblock->systemgap = v;
              }
            break;

            case b_sgnext:            /* delayed change of system gap */
              {
              b_sgstr *pp = (b_sgstr *)p;
              int v = pp->value;
              if (pp->opt == '+') page_sgnext += v; else page_sgnext = v;
              }
            break;


            case b_sshere:            /* immediate change of stave spacing */
              {
              b_ssstr *pp = (b_ssstr *)p;
              int s = pp->stave;
              int v = pp->value;

              if (!hadsshere)
                {
                page_sysblock->stavespacing =
                  store_copy(page_sysblock->stavespacing);
                hadsshere = TRUE;
                }

              if (s == 0)
                {
                int i;
                for (i = 1; i < page_lastwanted; i++)
                  {
                  if (pp->opt == '+')  
                    page_sysblock->stavespacing[i] += v;
                  else   
                    page_sysblock->stavespacing[i] = v;
                  }   
                }
              else if (pp->opt == '+')
                {
                page_sysblock->stavespacing[s] += v;
                if (page_ssehere != NULL)
                  {
                  int i;
                  for (i = s+1; i <= curmovt->laststave; i++)
                    {
                    if (mac_teststave(page_sysblock->notsuspend, i))
                      {
                      page_ssehere[i] += v;
                      break;
                      }
                    }
                  }
                }
              else page_sysblock->stavespacing[s] = v;
              }
            break;

            case b_ssnext:            /* delayed change of stave spacing */
              {
              b_ssstr *pp = (b_ssstr *)p;
              int s = pp->stave;
              int v = pp->value;
              if (!hadssnext)
                {
                page_ssnext = store_copy(page_ssnext);
                hadssnext = TRUE;
                }
              if (s == 0)
                {
                int i;
                for (i = 1; i < page_lastwanted; i++) 
                  {
                  if (pp->opt == '+')  
                    page_ssnext[i] += v;
                  else   
                    page_ssnext[i] = v;
                  }   
                }
              else if (pp->opt == '+') page_ssnext[s] += v;
                else page_ssnext[s] = v;
              }
            break;

            /* Changes to vertical justification and page positioning are put
            into system-specific variables, as it isn't known at the time to
            which page they will apply. Changes to horizontal justification are
            put directly into the justifyLR variable, as they apply to this
            system. */

            case b_justify:
              {
              b_justifystr *pp = (b_justifystr *)p;
              int horiz = pp->side & (just_left|just_right);
              int vert = pp->side & (just_top|just_bottom);

              if (horiz)
                page_justifyLR = (pp->opt == '+')?
                  (page_justifyLR | pp->side) : (page_justifyLR & ~pp->side);

              if (vert)
                {
                int oldjustify = (page_sys_justify == -1)?
                  page_justify : page_sys_justify;
                page_sys_justify = (pp->opt == '+')?
                  (oldjustify | pp->side) : (oldjustify & ~pp->side);
                }
              }
            break;

            case b_page:
              {
              b_pagestr *pg = (b_pagestr *)p;
              int value = pg->value;
              if (pg->relative == '+') value += curpage->number;
              if (value < curpage->number) 
                error_moan(112, value, curpage->number, bar, stave);
              else 
                curpage->number = value;
              }       
            break; 

            case b_pagebots:
            page_sys_botmargin = ((b_pagebotsstr *)p)->value;
            break;

            case b_pagetops:
            page_sys_topmargin = ((b_pagetopsstr *)p)->value;
            break;


            /* The only text we are interested in here is underlay; set up or
            remove continuation control blocks. */

            case b_text:
              {
              b_textstr *t = (b_textstr *)p;
              if ((t->flags & text_ul) != 0 &&
                  (t->ulen != 1 || t->string[0] != '='))
                {
                int c = t->string[t->ulen];
                ulaystr **uu = &bar_cont->ulay;
                ulaystr *u = *uu;

                /* On hitting any {und,ov}erlay, clear the field and flag so
                that only relevant notes are counted. Flag the type for the
                next note. */

                if ((t->flags & text_above) == 0)
                  {
                  hadulay = TRUE;
                  if (no_ulay)
                    {
                    page_sysblock->ulevel[stave] = 0;
                    no_ulay = FALSE;
                    }
                  }
                else
                  {
                  hadolay = TRUE;
                  if (no_olay)
                    {
                    page_sysblock->olevel[stave] = 0;
                    no_olay = FALSE;
                    }
                  }

                /* Find existing control block for this level */

                while (u != NULL && u->level != t->ulevel)
                  {
                  uu = &u->next;
                  u = *uu;
                  }

                /* If control block needed, either carry on with this one or
                get a new one. */

                if (c == '=' || c == '-')
                  {
                  if (u == NULL)
                    {
                    u = store_Xget(sizeof(ulaystr));
                    u->next = NULL;
                    u->x = u->y = 0;
                    u->level = t->ulevel;
                    *uu = u;
                    }
                  u->type = c;
                  u->htype = t->htype;
                  }

                /* Else free an existing one */

                else if (u != NULL)
                  {
                  *uu = u->next;
                  store_free(u);
                  }
                }
              }
            break;


            /* Handle changes of underlay level */

            case b_ulevel:
              {
              b_ulevelstr *pp = (b_ulevelstr *)p;
              page_ulevel[stave] = (pp->opt)? BIGNUMBER : pp->value;
              }
            break;

            case b_ulhere:
            page_ulhere[stave] = ((b_ulherestr *)p)->value;
            break;

            /* Handle changes of overlay level */

            case b_olevel:
              {
              b_olevelstr *pp = (b_olevelstr *)p;
              page_olevel[stave] = (pp->opt)? BIGNUMBER : pp->value;
              }
            break;

            case b_olhere:
            page_olhere[stave] = ((b_olherestr *)p)->value;
            break;

            /* Suspend sets flag for start of next system */

            case b_suspend:
            mac_clearstave(page_accepteddata->notsuspend, stave);
            break;

            /* We must cope with resume following suspend in the
            same system. */

            case b_resume:
            mac_setstave(page_accepteddata->notsuspend, stave);
            break;

            /* Actions that are common to this scan and to the output scan are
            held in a separate function. */

            default:
            misc_commoncont(p);
            break;
            }

          if (type == b_End) break;

          p = (bstr *)((uschar *)p + length_table[type]);
          type = p->type;
          }
        }
      }
    }
  }

/* At the end of a system we must check on the {und,ov}erlay continuation
control blocks. For each stave that has such a block (or blocks) we must look
at the next bar. If it does not exist, is emtpy, or starts with a rest, we must
kill the continuation block(s) for extender lines. Don't do this for hyphen
blocks - another syllable is always expected and there are odd cases when these
do go over rests, etc. We have to use a GOTO to get out of a switch within a
loop.

In the same loop we can deal with {und,ov}erlay levels. */

for (stave = 0; stave <= page_lastwanted; stave++)
  {
  ulaystr **uu;
  ulaystr *u;
  bar_cont = page_cont + stave;

  if (mac_teststave(curmovt->staves, stave) && bar_cont->ulay != NULL)
    {
    if (bar <= curmovt->barcount)
      {
      bstr *p = ((curmovt->stavetable)[stave])->barindex[bar];
      if (p != NULL && p->type != b_End)
        {
        int type = p->type;

        while (type != b_End)
          {
          switch(type)
            {
            case b_Jump:
            p = (bstr *)(((b_Jumpstr *)p)->next);
            break;

            case b_note:
            if (((b_notestr *)p)->spitch != 0) goto NOREMOVE;
              else goto REMOVE;
            }

          p = (bstr *)((uschar *)p + length_table[type]);
          type = p->type;
          }
        }
      }

    /* Remove the underlay blocks for extender lines. */

    REMOVE:

    uu = &(bar_cont->ulay);
    u = *uu;

    while (u != NULL)
      {
      if (u->type == '=')
        {
        *uu = u->next;
        store_free(u);
        }
      else uu = &(u->next);
      u = *uu;
     }
    }

  NOREMOVE:

  /* Handle changes to {und,ov}erlay levels */

  if (page_ulevel[stave] != BIGNUMBER)
    page_sysblock->ulevel[stave] = page_ulevel[stave];
  page_sysblock->ulevel[stave] += page_ulhere[stave];

  if (page_olevel[stave] != BIGNUMBER)
    page_sysblock->olevel[stave] = page_olevel[stave];
  page_sysblock->olevel[stave] += page_olhere[stave];
  }
}



/**************************************************
* Compute bounding y value, with accents and ties *
**************************************************/

/* The parameters of the note are in the n_* variables. The yield is a y value
relative to the staff base, with positive values going upwards. At the notehead
end of a note, it is one point away from the notehead. We calculate in
stave-points. The dynflag variable request inclusion of *all* dynamics;
otherwise include only those that go inside the stave (staccato, ring, bar).
This call is used when printing those that go outside the stave.

Arguments:
  below       TRUE for the bottom bound, FALSE for the top bound 
  tie         a tiestr for the note, or NULL if not tied
  accflag     TRUE if there's an accidental
  dynflag     TRUE if there are dynamics
  
Returns:      the y value    
*/

int 
misc_ybound(BOOL below, b_tiestr *tie, BOOL accflag, BOOL dynflag)
{
int yield;
int extra = 0;
int accextra = 0;
int flags = n_flags;
int acflags = n_acflags;

/* If this is a rest, the only parameter of interest is the rest level */

if (n_pitch == 0)
  return n_restlevel + resttable[n_notetype + (below? 8:0)];

/* Deal with a note; first calculate additional length for stem, if any */

if ((flags & nf_stem) != 0)
  {
  extra = mac_muldiv(n_stemlength + 12000, n_fontsize, 10000);
  if (n_beamed)
    {
    extra += 1000;

    /* Extra for all but the first note of steep downward beams when stems
    are up. */

    if (n_upflag && beam_slope < 0 && n_lastnote != beam_first)
      extra += 5*abs(beam_slope);
    }
  }


/* The basic value takes account of the appropriate pitch and, if relevant, any
accidental. We remember, in accextra, additional space added here. It will be
taken away if there is subsequent space added for an accent. */

if (below)
  {
  if (n_upflag)
    {
    extra = accflag? accdowntab[n_lastacc] : 0;
    accextra = -extra;
    }
  else
    {
    if (accflag && extra == 0)
      {
      extra = accdowntab[n_firstacc];
      accextra = -extra;
      }
    }
  if (extra == 0)
    {
    extra = 1000;
    accextra = -extra;
    }
  yield = (n_minpitch - 130)*1000 - extra;
  }

else
  {
  if (n_upflag)
    {
    if (accflag && extra == 0)
      {
      extra = accuptab[n_firstacc];
      accextra = extra;
      }
    }
  else
    {
    extra = accflag? accuptab[n_lastacc] : 0;
    accextra = extra;
    }
  if (extra == 0)
    {
    extra = 1000;
    accextra = extra;
    }
  yield = (n_maxpitch - 126)*1000 + extra;
  }


/* Allow for ties */

if (tie != NULL)
  {
  if (below)
    {
    if (tie->belowcount > 0 && (n_upflag || (flags & nf_stem) == 0))
      yield -= 4000;
    }
  else
    {
    if (tie->abovecount > 0 && (!n_upflag || (flags & nf_stem) == 0))
      yield += 4000;
    }
  }


/* Allow for dynamics. First of all, get rid of bowing marks if they are not
relevant. */

if ((bar_cont->flags & cf_bowingabove) == 0)
  {
  if (!below) acflags &= ~(af_up | af_down); 
  }
else
  {
  if (below) acflags &= ~(af_up | af_down); 
  }     


if ((acflags & af_dynamics) != 0 && (dynflag || (acflags & af_dyninside) != 0))
  {
  int oppflag, dynextra;

  if ((acflags & (af_dynamics - af_up - af_down)) != 0)
    {
    oppflag = ((acflags & af_opposite) == 0)? 0:1;
    }
  else
    {
    if ((bar_cont->flags & cf_bowingabove) == 0)
      oppflag = n_upflag? 0:1;
    else oppflag = n_upflag? 1:0;
    }

  dynextra = accboundtable[oppflag + (n_upflag? 2:0) + (below? 4:0)];

  /* If adding space for an accent, retract space for an accidental */

  if (dynextra) yield += dynextra - accextra;

  /* That's all if no relevant accent, or the accent falls inside the staff;
  otherwise make sure the pitch is suitably outside the staff. */

  if (dynextra != 0 && dynflag && (acflags & af_dynoutside) != 0)
    {
    yield += dynextra;   /* these are bigger accents */

    if (below)
      {
      if (yield > -10000) yield = -10000;
      }
    else if (yield < 22000) yield = 22000;
    }
  }

return yield;
}


/* End of misc.c */
