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

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

/* Written by Philip Hazel, starting November 1991 */
/* This file last modified: August 2019 */


/* This file contains subroutines called by the code for creating a position
table for a bar. */


#include "pmwhdr.h"
#include "pagehdr.h"
#include "poshdr.h"




/*************************************************
*             Make space for non-note            *
*************************************************/

/* In fact, this is used for grace notes as well. The musical offset supplied
is always negative. We have to check whether a position for the auxiliary item
already exists, and if so, to adjust its place if necessary. The yield is the
new value for this position. The "here" flag determines whether the item can be
put to the left of the current position if there is space.

Arguments:
  previous     pointer to postable entry for the previous position, or NULL
  this         pointer to current postable entry
  auxoffset    offset for the non-note item (always negative)
  Rwidth       width for the item
  used
  here         TRUE if

Returns:       pointer to the postable entry after "this"
*/

static workposstr *
  pos_insertXpos(workposstr *previous, workposstr *this, int auxoffset,
  int Rwidth, int used, BOOL here)
{
workposstr *aux = NULL;

Rwidth = mac_muldiv(Rwidth, main_stavemagn, 1000);

/* See if any auxiliaries exist, and remember if this one */

if (this > page_postable)
  {
  workposstr *t = this - 1;
  if (t->auxid != 0)
    {
    while (t >= page_postable && t->auxid != 0)
      {
      aux = t;
      t--;
      }
    }
  }

/* If there are no auxiliaries, the space available is the sum from the
previous position (if any) to this position. If previously existing auxiliaries
are to the right of this one, the same thing applies. We can do a straight
insert with an appropriate offset in both cases. */

if (aux == NULL || auxoffset < aux->auxid)
  {
  workposstr *t;
  workposstr *insertpoint = (aux == NULL)? this : aux;

  for (t = page_posptr; t >= insertpoint; t--)   /* Move up to make space */
    memcpy(t+1, t, sizeof(workposstr));

  page_posptr++;                 /* increase the end */
  this++;                        /* for yielding */

  aux = insertpoint++;

  insertpoint->space = 0;       /* leave space on the new insert */
  aux->moff = this->moff + auxoffset;
  aux->auxid = auxoffset;       /* identifies the aux */
  aux->posstaves = page_stave;  /* flags which stave */

  if (here) aux->xoff = insertpoint->xoff; else
    {
    int avail = 0;
    if (previous != NULL)
      {
      t = previous + 1;
      while (t <= aux) avail += (t++)->xoff;
      }
    avail -= Rwidth + used;
    if (avail < 0) insertpoint->xoff -= avail;
    aux->xoff = insertpoint->xoff - Rwidth;
    }

  insertpoint->xoff = Rwidth;
  }

/* Either there are auxiliaries to the left of this one, or this one already
exists. */

else
  {
  workposstr *insertpoint = aux;

  while (insertpoint != this)
    {
    if (insertpoint->auxid >= auxoffset) break;
    insertpoint++;
    }

  /* If this auxiliary already exists, test that there is enough space between
  it and the previous note. This works because we process the auxiliaries for
  one note from right to left. In the case of accidentals, we must adjust the
  space by the difference between this accidental's requirements and whatever
  is already there. */

  if (insertpoint->auxid == auxoffset)
    {
    int avail = 0;
    workposstr *next = insertpoint + 1;
    workposstr *t = (previous == NULL)? page_postable : previous + 1;

    while (t <= insertpoint) avail += (t++)->xoff;

    /* For non-accidentals, check that there is enough space to the right,
    and increase if necessary. */

    if (auxoffset != posx_acc)
      { if (Rwidth > next->xoff) next->xoff = Rwidth; }

    /* For accidentals, all we need to do is check that there is enough space
    on the left, since accidental printing doesn't actually use the calculated
    position - chords need several positions, for a start. */

    else avail += next->xoff - Rwidth;

    /* If there is insufficient space, move *all* the auxiliaries
    to the right. */

    avail -= used;
    if (avail < 0) aux->xoff -= avail;

    /* Flag which staves */

    insertpoint->posstaves |= page_stave;
    }

  /* If this auxiliary does not exist, we must insert it. See if there is space
  on *all* staves between the first auxiliary and the previous note position.
  If there is, we can move those auxiliaries to the left of this one to the
  left. */

  else
    {
    workposstr *t;
    workposstr *new = insertpoint;

    for (t = page_posptr; t >= insertpoint; t--)   /* Move up to make a space */
      memcpy(t+1, t, sizeof(workposstr));

    page_posptr++;                  /* list is one longer now */
    this++;                         /* for yielding */
    insertpoint++;

    new->space = 0;
    new->moff = this->moff + auxoffset;
    new->auxid = auxoffset;         /* identifies the aux */
    new->posstaves = page_stave;    /* flag which stave */

    /* Distance from previous aux is its standard distance */

    new->xoff = insertpoint->xoff;
    insertpoint->xoff = Rwidth;

    if (!here)
      {
      int avail = aux->xoff - Rwidth - used;
      if (avail > -Rwidth)
        aux->xoff -= (avail < 0)? Rwidth + avail : Rwidth;
      }
    }
  }

return this;
}




/*************************************************
*           Insert non-note items                *
*************************************************/

/* This function is called to insert entries into the postable for non-note
things such as accidentals and other markings.

Arguments:
  moff          music offset for the note
  xflags        flags for various marks (comma, caesura, etc)
  accleft       width required for accidental
  keyvector
  timevector
  gracevector
  previous      pointer to the previous position item
  prevlength
  prevflags

Returns:        pointer to final inserted item
*/

workposstr *
  pos_insertextras(int moff, int xflags, int accleft, int *keyvector,
  int *timevector, int *gracevector, workposstr *previous, int prevlength,
  int prevflags)
{
workposstr *this;
int rrepeatextra = 0;
int used;
int i;

/* Find the postable entry for this note */

this = (previous == NULL)? page_postable : previous;
while (this->moff < moff) this++;

/* Now process the auxiliary items, in order from right to left. This makes it
possible to do the best thing when more than one exists on a single note. They
may come out of order when on different notes, and in this case the spacing may
not be as perfect. But the likelihood is pretty rare.

They are given conventional "musical offsets" that are just prior to that of
the note they precede.

First handle accidentals. Because these are much more common than anything
else, it's worth a special test to avoid the rest of the procedure if there's
nothing else to do. Note that "this" might already be pointing at an accidental
entry -- so that extra space isn't inserted after it. Preserve this state of
affairs. We compute "used" here specially, with a different default to the
rest. */

if (accleft > 0)
  {
  used = (prevlength < 0)? 0 : pos_typewidth(7250, prevlength, prevflags);
  this = pos_insertXpos(previous, this, posx_acc, accleft, used, FALSE);
  if (xflags == 0) return this;
  }

/* Insert positions for grace notes. This is messy. We give the widths
as including the accidental widths so as to space them out correctly,
but then we have to go back afterwards and correct the positions. We
also have to take special action if there are already grace notes at
this position, in case the accidentals are different (rare). "Used"
also gets a special value for grace notes. */

if (gracevector[0] > 0)
  {
  int lastspacing = curmovt->gracespacing[0];
  int first = 0;

  workposstr *pp = this;
  workposstr *p = this - 1;

  if (this >= page_posptr)
    lastspacing += ((xflags & xf_rrepeat) == 0)? 4000 : 8000;

  used = (prevlength < 0)? 0 : pos_typewidth(7250, prevlength, prevflags);

  /* As we process them from right to left, any new ones will be inserted
  first. When we get to pre-existing ones, save where to stop the moving scan,
  and instead just check existing widths. */

  for (i = gracevector[0]; i >= 1; i--)
    {
    int id = posx_gracefirst + i - 1;
    if (pp == page_postable || p->auxid != id)
      {
      this = pos_insertXpos(previous, this, id,
        ((i == gracevector[0])? lastspacing : curmovt->gracespacing[1]) +
          gracevector[i], used, FALSE);
      }
    else
      {
      int spacing = (p->xoff == 0)? 0 : curmovt->gracespacing[1];

      /* When we hit what was the last (rightmost) gracenote, but is no longer,
      adjust the appropriate spacing, just in case the gracespacing values are
      different. This is the first of the pre-existing grace notes. Remember
      which it was. */

      if (first == 0)
        {
        int adjust = curmovt->gracespacing[1] - curmovt->gracespacing[0];
        (p+1)->xoff += adjust;
        first = i;
        }

      if (p->xoff < spacing + gracevector[i])
        p->xoff = spacing + gracevector[i];
      p--;
      pp--;
      }
    }

  /* Go back from right to left and adjust positions, starting at the first
  additional grace note, and stopping when we get to any that were there
  before. */

  p = this - 1;
  while (p->auxid == posx_acc) p--;
  pp = p + 1;

  for (i = gracevector[0]; i > first; i--)
    {
    p->xoff += gracevector[i];
    pp->xoff -= gracevector[i];
    pp = p--;
    }
  }

/* Compute the space used by the previous note, if any, for the rest of the
items. */

used = (prevlength < 0)? 0 : pos_typewidth(11000, prevlength, prevflags);

/* Time Signature(s) */

for (i = timevector[0]; i >= 1; i--)
  this = pos_insertXpos(previous, this, posx_timefirst + i - 1,
    timevector[i], used, FALSE);

/* Key Signature(s) */

for (i = keyvector[0]; i >= 1; i--)
  this = pos_insertXpos(previous, this, posx_keyfirst + i - 1,
    keyvector[i], used, FALSE);

/* Left repeat. Check whether it is going to print on a bar line, and if not,
insert a position for it, unless it is going to coincide with a right repeat.
If it is on a barline, set a flag so that space can be inserted. (Can't insert
here, 'cause it will then do it several times for multiple staves.) */

if ((xflags & xf_lrepeat) != 0)
  {
  if (previous == NULL && !page_startlinebar && pos_bp->posxRL == -posx_RLleft)
    pos_barstartrepeat = TRUE;
  else if (previous != NULL && (xflags & xf_rrepeat) != 0)
    rrepeatextra = 6500;
  else
    this = pos_insertXpos(previous, this, -pos_bp->posxRL, 12500, used, TRUE);
  }

if ((xflags & xf_clef) != 0)
  this = pos_insertXpos(previous, this, posx_clef,
    (14 * (curmovt->fontsizes)->fontsize_clefs)/10, used, FALSE);

if ((xflags & xf_rrepeat) != 0)
  {
  int x = 7500;
  if (this == page_posptr) page_lastendwide = TRUE; else x += 5100;
  this = pos_insertXpos(previous, this, posx_RR, x+rrepeatextra, used, TRUE);
  }

if ((xflags & xf_dotbar) != 0)
  this = pos_insertXpos(previous, this, posx_dotbar, 6000, used, FALSE);

if ((xflags & xf_tick) != 0)
  this = pos_insertXpos(previous, this, posx_tick, 6000, used, FALSE);

if ((xflags & xf_comma) != 0)
  this = pos_insertXpos(previous, this, posx_comma, 6000, used, FALSE);

if ((xflags & xf_caesura) != 0)
  this = pos_insertXpos(previous, this, posx_caesura, 13000, used, FALSE);

return this;    /* return for next previous */
}



/*************************************************
*         Calculate typographic width            *
*************************************************/

/* This procedure calculates the magnified typographic width for a note of
given length, with given flags. For a chord, the flags should be the 'or' of
those for all the notes.

Arguments:
  used       the basic minimum width for the note (unmagnified)
  length     the note's musical length (identifies the note)
  flags      the note's flags

Returns:     the valued of "used" plus the calculated width
*/

int
pos_typewidth(int used, int length, int flags)
{
/* Invisible notes use nothing, breves and semibreves need some extra, as do
freestanding upquavers (to allow for the tail on the right). */

if ((flags & nf_hidden) != 0) return 0;

if (length >= len_breve) used += 3300;
  else if (length >= len_semibreve) used += 800;
    else if ((flags & nf_fuq) != 0) used += 5000;

/* Extra width for chord with inverted note and stem up, and even more if
dotted. */

if ( (flags & (nf_stemup|nf_invert)) == (nf_stemup|nf_invert) && used < 12400)
  {
  used = 12400;
  if ((flags & (nf_dot | nf_plus)) != 0) used += 2000;
  }

/* Extra width for dots or plus */

if ((flags & nf_plus) != 0) used += 8000;
  else if ((flags & nf_dot) != 0)
    {
    used += 3000;
    if ((flags & nf_dot2) != 0) used += 3500;
    }

/* Allow for magnification */

return mac_muldiv(used, main_stavemagn, 1000);
}




/*************************************************
*          Calculate horizontal width            *
*************************************************/

/* This procedure calculates the magnified basic horizontal width required for
a note of a given musical length.

Argument:   the note's musical length
Returns:    the magnified width
*/

int
pos_notewidth(int length)
{
int thislength = len_breve;
int type = breve;
int offset;

while (length < thislength)
  {
  type++;
  thislength /= 2;
  }

/* Notes shorter than a hemi-demi-semiquaver can only be created by triplets or
the like. These are unlikely ever to occur. Just ensure something non-crashy
happens. */

if (type > hdsquaver) { offset = 0; type = hdsquaver + 1; }
  else offset = page_nextdata->notespacing[type];

if (length != thislength)
  {
  int extra = length - thislength;
  if (extra == thislength/2)
    {
    offset = mac_muldiv(offset, curmovt->dotspacefactor, 1000);
    }
  else if (extra == (3*thislength)/4)
    offset = mac_muldiv(offset, 3*curmovt->dotspacefactor-1000, 2000);

  /* We have a triplet or similar. Breve triplets are rarer than hen's teeth,
  so fudge the "next note" spacing to make things work. We then set the offset
  as a pro rata amount between the relevant two kinds of note. */

  else
    {
    int nextup = (type == breve)?
      (3*offset)/2 : page_nextdata->notespacing[type-1];
    offset += mac_muldiv(nextup-offset, extra, thislength);
    }
  }

/* When we are re-evaluating bar widths because a system is being squashed,
it works best if the notewidths themselves are squashed at this point. The
layout of the underlay can then be better computed. */

if (page_layout_stretchn < page_layout_stretchd)
  offset = mac_muldiv(offset, page_layout_stretchn, page_layout_stretchd);

/* Now apply the magnification factor and return */

return mac_muldiv(offset, main_stavemagn, 1000);
}

/* End of possubs.c */
