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

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

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


/* This file contains part II of the code for reading in a PMW score file. It
deals with heading information. The main control function is last, preceded by
a table of heading directives which refers to earlier functions for processing
them. */


#include "pmwhdr.h"
#include "readhdr.h"


#define oo offsetof

/* Options for reading integer values */

#define int_u       1    /* unsigned */
#define int_s       2    /* signed */
#define int_rs      3    /* relative if signed */
#define int_less1   4    /* take away 1 (flag) */
#define int_f       8    /* fixed point (flag, must be last) */

/* Data for the b2pffont directive */

#ifdef SUPPORT_B2PF
typedef struct b2pfopt {
  const char *name;
  uint32_t option;
} b2pfopt;

static b2pfopt b2pf_options[] = {
  { "input_backchars", B2PF_INPUT_BACKCHARS },
  { "input_backcodes", B2PF_INPUT_BACKCODES },
  { "output_backchars", B2PF_OUTPUT_BACKCHARS },
  { "output_backcodes", B2PF_OUTPUT_BACKCODES }
};

#define b2pf_options_size (sizeof(b2pf_options)/sizeof(b2pfopt))
#endif


/*************************************************
*            Global variable list                *
*************************************************/

/* Since we cannot put global addresses directly into the static table of
structures, we have to indirect them via a separate vector. (This restriction
is in the C language.) */

static int *global_vars[] = {
  (int *)(&main_magnification),
  (int *)(&main_maxvertjustify),
  (int *)(&midi_for_notes_off),
  (int *)(&opt_oldbeambreak),
  (int *)(&opt_oldrestlevel),
  (int *)(&opt_oldstemlength),
  (int *)(&main_truepagelength),
  (int *)(&main_righttoleft),
  (int *)(&main_sheetheight),
  (int *)(&main_sheetwidth),
  (int *)(&opt_stretchrule),
  (int *)(&main_kerning)
};

enum {
  glob_magnification,
  glob_maxvertjustify,
  glob_midifornotesoff,
  glob_oldbeambreak,
  glob_oldrestlevel,
  glob_oldstemlength,
  glob_pagelength,
  glob_righttoleft,
  glob_sheetdepth,
  glob_sheetwidth,
  glob_stretchrule,
  glob_kerning
};


/*************************************************
*          Local static variables                *
*************************************************/

static int read_map[STAVE_BITVEC_SIZE];  /* For reading lists of staves */


/*************************************************
*        Read and set font name, if present      *
*************************************************/

/* If a recognized font word ("roman", "italic", etc.) is the next thing in the
input, read it and put its index number into the current movement data
structure, at a given offset. If what follows is not a recognized font word, do
nothing.

Argument:   the offset for the result
Returns:    nothing
*/

static void
set_fontname(int offset)
{
sigch();
if (isalpha(read_ch))
  {
  uschar *save_readchptr = read_chptr;
  int save_readch = read_ch;
  int x;
  if ((x = font_fontword(TRUE)) > 0)
    {
    *((int *)(((uschar *)curmovt) + offset)) = x;
    }
  else
    {
    read_ch = save_readch;
    read_chptr = save_readchptr;
    }
  }
}



/*************************************************
*           Read and set font size               *
*************************************************/

/* This is for the table of font sizes in the movement structure. If the next
thing in the input is a digit, read it as a font size. If not, do nothing.
The size may be followed by a stretch and shear, if permitted.

Arguments:
  offset      offset in current movement data structure for the size result
  stretchOK   TRUE if stretch and/or shearing are permitted for the font

Returns:      nothing
*/

static void
set_fontsize(int offset, BOOL stretchOK)
{
sigch();
if (isdigit(read_ch))
  {
  int *sizeptr, **matrixptr;

  if (!read_copied_fontsizestr)
    {
    fontsizestr *new = store_Xget(sizeof(fontsizestr));
    *new = *(curmovt->fontsizes);
    curmovt->fontsizes = new;
    read_copied_fontsizestr = TRUE;
    }

  /* Compute the addresses of where to put the size and the stretch/shear
  matrix, allowing for the fact that pointers may not be the same size as ints,
  an error that wasn't fixed till February 2014. */

  sizeptr = (int *)(((uschar *)(curmovt->fontsizes)) + offset);
  matrixptr = (int **)((uschar *)(curmovt->fontsizes) +
              oo(fontsizestr, fontmatrix_music) +
              (offset/sizeof(int)) * sizeof(int *));

  /* Read size, default no matrix */

  *sizeptr = read_integer(TRUE);
  *matrixptr = NULL;

  /* Handle stretch and shear */

  if (read_ch == '/')
    {
    int stretch;
    int shear = 0;
    next_ch();
    if (!read_expect_integer(&stretch, TRUE, FALSE)) return;
    if (read_ch == '/')
      {
      next_ch();
      if (!read_expect_integer(&shear, TRUE, TRUE)) return;
      }
    if (!stretchOK) error_moan(ERR97, "allowed", "with this directive"); else
      {
      int *matrix = store_Xget(4*sizeof(int));
      *matrixptr = matrix;
      matrix[0] = mac_muldiv(stretch, 65536, 1000);
      matrix[1] = 0;
      matrix[2] = (int)(tan(((double)shear)*atan(1.0)/45000.0)*65536.0);
      matrix[3] = 65536;
      }
    }
  }
}


/*******************************************************************
*  The following functions are referenced in the read_headstring   *
*  vector of heading directives. Each is called when the relevant  *
*  directive is encountered. They all have the same interface:     *
*  read_dir->arg1 and read_dir->arg2 contain up to two arguments   *
*  associated with the directive. There are no arguments to the    *
*  functions themselves, and they do not return any values.        *
******************************************************************/


/*************************************************
*           Deal with plain font size            *
*************************************************/

static void
movt_fszproc(void)
{
sigch();
if (isdigit(read_ch)) set_fontsize(read_dir->arg1, read_dir->arg2);
  else error_moan(ERR10, "Number");
}


/*************************************************
*           Deal with heading boolean            *
*************************************************/

static void
movt_cboolproc(void)
{
*((CBOOL *)((uschar *)curmovt + read_dir->arg1)) = read_dir->arg2;
}


/*************************************************
*            Deal with heading font              *
*************************************************/

static void
movt_fontproc(void)
{
int x;
set_fontsize(read_dir->arg2, TRUE);
if ((x = font_fontword(FALSE)) > 0)
  *((int *)(((uschar *)curmovt) + read_dir->arg1)) = x;
}


/*************************************************
*           Deal with global boolean             *
*************************************************/

static void
opt_boolproc(void)
{
if (main_lastmovement == 1)
  *global_vars[read_dir->arg1] = read_dir->arg2;
else error_moan(ERR20);
}


/*************************************************
*           Deal with heading integer            *
*************************************************/

static void
movt_intproc(void)
{
int x;
int value = 0;
int flags = read_dir->arg2;
int offset = ((flags & int_less1) != 0)? -1 : 0;
int arg2 = flags & ~(int_f | int_less1);

int *address = (int *)((uschar *)curmovt + read_dir->arg1);
sigch();
if (arg2 == int_rs && (read_ch == '+' || read_ch == '-')) value = *address;
if (read_expect_integer(&x, flags >= int_f, arg2 != int_u))
  *address = value + x + offset;
}


/*************************************************
*           Deal with global integer            *
*************************************************/

static void
opt_intproc(void)
{
int x;
int *address = global_vars[read_dir->arg1];
int value = 0;
int arg2 = (read_dir->arg2) & ~int_f;
sigch();
if (arg2 == int_rs && (read_ch == '+' || read_ch == '-')) value = *address;
if (read_expect_integer(&x, read_dir->arg2 >= int_f, arg2 != int_u))
  *address = value + x;
if (main_lastmovement != 1) error_moan(ERR20);
}


/*************************************************
*          Deal with heading stave list          *
*************************************************/

static void
movt_listproc(void)
{
stave_list **p = (stave_list **)(((uschar *)curmovt) + read_dir->arg1);
*p = NULL;

sigch();
while (isdigit(read_ch))
  {
  int s = read_integer(FALSE);
  int t = s;
  sigch();
  if (read_ch == '-')
    {
    next_sigch();
    if (!read_expect_integer(&t, FALSE, TRUE)) return;
    }

  if (t < s) error_moan(ERR21);
    else if (t > MAX_STAVE) error_moan(ERR22, MAX_STAVE+1);
  else
    {
    stave_list *q = store_Xget(sizeof(stave_list));
    q->next = NULL;
    q->first = s;
    q->last = t;
    *p = q;
    p = &(q->next);
    }

  sigch();
  if (read_ch == ',') next_sigch();
  }
}


/*************************************************
*          Deal with heading stave map           *
*************************************************/

static void
movt_mapproc(void)
{
mac_initstave(read_map, 0);
if (read_dir->arg2 >= 0) mac_setstave(read_map, read_dir->arg2);

sigch();
while (isdigit(read_ch))
  {
  int s = read_integer(FALSE);
  int t = s;
  sigchNL();
  if (read_ch == '-')
    {
    next_sigch();
    if (!read_expect_integer(&t, FALSE, TRUE)) return;
    }

  if (t < s) error_moan(ERR21);
    else if (t > MAX_STAVE) error_moan(ERR22, MAX_STAVE + 1);
      else while (s <= t) { mac_setstave(read_map, s); s++; }  /* Can't put */
                                                               /* s++ inside */
  sigchNL();
  if (read_ch == ',') next_sigch();
  }

if (read_dir->arg1 != 0)
  memcpy(((uschar *)curmovt) + read_dir->arg1, read_map,
    STAVE_BITVEC_SIZE*sizeof(int));
}


/*************************************************
*          Deal with heading/footing text        *
*************************************************/

/* The bits in the map remember which have been read, for flushing at the start
of a new movement. */

static void
movt_headproc(void)
{
headstr **oldp = (headstr **)(((uschar *)curmovt) + read_dir->arg1);
headstr *new = store_Xget(sizeof(headstr));

/* If this is the first one of this type of heading, reset the pointer
so as not to copy the ones from the previous movement. */

if ((read_headmap & read_dir->arg2) == 0)
  {
  read_headmap |= (read_dir->arg2 & ~rh_ps);
  *oldp = NULL;
  }

/* Else find the end of the chain */

else while (*oldp != NULL) oldp = &((*oldp)->next);

/* Add the new block onto the chain and initialize */

*oldp = new;

read_headfootingtext(new, read_dir->arg2);
}


/*************************************************
*        Read arguments for heading/footing      *
*************************************************/

/* This code is also called from read4 for reading the text of footnotes and
sysnotes, so it has to be globally addressible. */

void
read_headfootingtext(headstr *new, int type)
{
int defaultsize = 0;
BOOL is_pshdr = (type & rh_ps) != 0;

new->next = NULL;
new->stretch = 0;
new->font = font_rm;

/* For a non-PostScript header, if the next character is a letter, we expect to
read "draw <name>"; otherwise we expect an optional type size and a text
string. */

sigch();
if (!is_pshdr && isalpha(read_ch))
  {
  tree_node *node;
  int argcount = 0;
  drawitem *drawargs = NULL;
  drawitem args[20];

  next_word();
  if (Ustrcmp(read_word, "draw") != 0)
    {
    error_moan(ERR10, "\"draw\"");
    return;
    }

  sigch();
  while (isdigit(read_ch) || read_ch == '-' || read_ch == '+' || read_ch == '\"')
    {
    if (read_ch == '\"')
      {
      args[++argcount].d.ptr = read_draw_text();
      args[argcount].dtype = dd_text;
      }
    else
      {
      if (!read_expect_integer(&(args[++argcount].d.val), TRUE, TRUE)) break;
      args[argcount].dtype = dd_number;
      }
    sigch();
    }

  if (argcount > 0)
    {
    int i;
    drawargs = store_Xget((argcount+1)*sizeof(drawitem));
    drawargs[0].dtype = dd_number;
    drawargs[0].d.val = argcount;
    for (i = 1; i <= argcount; i++) drawargs[i] = args[i];
    }

  next_word();
  node = Tree_Search(draw_tree, read_word);
  if (node == NULL)
    {
    error_moan(ERR70, read_word);
    return;
    }
  new->drawing = node;
  new->drawargs = drawargs;
  new->size = -1;
  }

/* Deal with PostScript and non-draw heading/footing */

else
  {
  /* In the non-PostScript case, set the type size, either by reading, or by
  algorithm. If explicit, can be followed by stretch and possibly shear,
  otherwise a NULL matrix. */

  new->matrix = NULL;
  if (!is_pshdr && isdigit(read_ch))
    {
    new->size = read_integer(TRUE);
    if (read_ch == '/')
      {
      int *matrix = store_Xget(4*sizeof(int));
      int stretch;
      int shear = 0;
      next_ch();
      if (!read_expect_integer(&stretch, TRUE, FALSE)) return;
      if (read_ch == '/')
        {
        next_ch();
        if (!read_expect_integer(&shear, TRUE, TRUE)) return;
        }
      new->matrix = matrix;
      matrix[0] = mac_muldiv(stretch, 65536, 1000);
      matrix[1] = 0;
      matrix[2] = (int)(tan(((double)shear)*atan(1.0)/45000.0)*65536.0);
      matrix[3] = 65536;
      }
    sigch();
    }
  else switch(type)
    {
    case rh_heading:
    new->size = read_headingsizes[read_headcount];
    break;

    case rh_footing:
    case rh_pagefooting:
    case rh_lastfooting:
    new->size = read_footingsize;
    break;

    case rh_pageheading:
    new->size = read_pageheadingsize;
    break;

    case rh_footnote:
    new->size = curmovt->fontsizes->fontsize_footnote;
    new->matrix = curmovt->fontsizes->fontmatrix_footnote;
    break;

    default:        /* PostScript headings/footings */
    new->size = 0;  /* This indicates that it's PostScript */
    break;
    }

  /* Default size is type size */

  defaultsize = new->size;

  /* Must increment headcount whether explicit size supplied or not */

  if (type == rh_heading && read_headcount < read_maxheadcount)
    read_headcount++;

  /* We now expect a heading string; if not PostScript, check its escapes */

  new->text = string_read();
  new->spaceabove = 0;
  if (new->text != NULL && !is_pshdr) new->text = string_check(new->text);
  }

/* The stretch value is set to zero here; if required it will be set later. */

new->stretch = 0;

/* For a non-PostScript header, if another number follows, it is the space that
follows the heading; if not, default it. */

sigch();
if (is_pshdr) new->space = 0; else
  {
  if (isdigit(read_ch) || ((read_ch == '+' || read_ch == '-') &&
      isdigit(*read_chptr)))
    read_expect_integer(&(new->space), TRUE, TRUE);
  else new->space = defaultsize;
  }
}


/*************************************************
*            Accadjusts                          *
*************************************************/

static void
accadjusts(void)
{
int i;
int *x = store_Xget(8*sizeof(int));
for (i = 0; i < 8; i++)
  {
  sigch();
  if (read_ch == ',') next_sigch();
  if (read_ch != '-' && read_ch != '+' && !isdigit(read_ch)) x[i] = 0;
    else (void)read_expect_integer(x+i, TRUE, TRUE);
  }
curmovt->accadjusts = x;
}


/*************************************************
*            Accspacing                          *
*************************************************/

static void
accspacing(void)
{
int i;
int *x = store_Xget(6*sizeof(int));
for (i = 1; i < 6; i++)
  {
  sigch();
  if (read_ch == ',') next_sigch();
  if (!read_expect_integer(x+i, TRUE, TRUE)) break;
  }
curmovt->accspacing = x;
}


/*************************************************
*       Set up font for B2PF processing          *
*************************************************/

/* The b2pffont directive is available only if explicitely selected when PMW is
built. It is permitted only in the first movement of a file (like textfont). */

static void
b2pffont(void)
{
#ifndef SUPPORT_B2PF
error_moan(ERR151);   /* Hard error: not supported */
#else

int fontid, rc;
unsigned int ln;
uint32_t options = 0;

if (main_lastmovement != 1) { error_moan(ERR20); return; }

sigch();
if ((fontid = font_fontword(FALSE)) < 0) return;
if (font_b2pf_contexts[fontid] != NULL) error_moan(ERR154);  /* Hard */

/* Read B2PF options */

sigch();
while (read_ch != '\"')
  {
  size_t i;
  next_word();
  for (i = 0; i < b2pf_options_size; i++)
    {
    if (Ustrcmp(read_word, b2pf_options[i].name) == 0)
      {
      options |= b2pf_options[i].option;
      break;
      }
    }
  if (i >= b2pf_options_size)
    {
    error_moan(ERR152, read_word);
    break;
    }
  sigch();
  }

font_b2pf_options[fontid] = options;  /* Remember processing options */

/* Read the B2PF context name */

if (!read_plainstring())
  {
  error_moan(ERR10, "B2PF context name (quoted)");
  return;
  }

/* Create a B2PF context for this font (there are currently no context create
options). */

rc = b2pf_context_create((const char *)read_word,
  (const char *)font_data_extra, 0, font_b2pf_contexts + fontid,
  NULL, NULL, NULL, &ln);

/* If there are more strings in quotes, add them as extra context rules. */

if (rc == B2PF_SUCCESS)
  {
  sigch();
  while (read_ch == '\"')
    {
    (void)read_plainstring();  /* Can't fail if leading quote seen */
    rc = b2pf_context_add_file(font_b2pf_contexts[fontid],
      (const char *)read_word, (const char *)font_data_extra, 0, &ln);
    if (rc != B2PF_SUCCESS) break;
    sigch();
    }
  }

if (rc != B2PF_SUCCESS)
  {
  size_t buffused;
  char buffer[128];
  (void)b2pf_get_error_message(rc, buffer, sizeof(buffer), &buffused, 0);
  buffer[buffused] = 0;
  error_moan(ERR155, buffer);  /* Hard */
  }

#endif  /* SUPPORT_B2PF */
}


/*************************************************
*               Barlinespace                     *
*************************************************/

static void
barlinespace(void)
{
sigch();
if (read_ch == '*')
  {
  curmovt->barlinespace = (int)0x80000000;
  next_sigch();
  }
else movt_intproc();
}


/*************************************************
*              Barnumberlevel                    *
*************************************************/

/* A sign is mandatory */

static void
barnumberlevel(void)
{
sigch();
if (read_ch != '+' && read_ch != '-') error_moan(ERR10, "\"+\" or \"-\"");
  else movt_intproc();
}


/*************************************************
*            Barnumbers                          *
*************************************************/

static void
barnumbers(void)
{
int wordread = FALSE;
curmovt->barno_textflags = 0;
sigch();
if (isalpha(read_ch))
  {
  next_word();
  sigch();
  if (Ustrcmp(read_word, "boxed") == 0) curmovt->barno_textflags = text_box;
    else if (Ustrcmp(read_word, "ringed") == 0) curmovt->barno_textflags = text_ring;
      else wordread = TRUE;
  }

if (!wordread && isalpha(read_ch))
  { next_word(); wordread = TRUE; }

if (wordread)
  {
  if (Ustrcmp(read_word, "line") == 0) curmovt->barno_interval = -1;
    else { error_moan(ERR10, "\"line\""); return; }
  }
else
  {
  if (!read_expect_integer(&(curmovt->barno_interval), FALSE, FALSE)) return;
  }

set_fontsize(oo(fontsizestr, fontsize_barno), TRUE);
set_fontname(oo(movtstr, font_barnumber));
}


/*************************************************
*              Bracestyle                        *
*************************************************/

static void
bracestyle(void)
{
movt_intproc();
if (curmovt->bracestyle > 1)
  {
  error_moan(ERR37, "Number less than 2");
  curmovt->bracestyle = 0;
  }
}


/*************************************************
*            Breakbarlines                       *
*************************************************/

static void
breakbarlines(void)
{
sigch();
if (!isdigit(read_ch))
  {
  usint *map = (usint *)(((uschar *)curmovt) + read_dir->arg1);
  mac_initstave(map, -1);
  }
else movt_mapproc();
curmovt->fullbarend = FALSE;
}


/*************************************************
*            Breakbarlinesx                      *
*************************************************/

static void
breakbarlinesx(void)
{
breakbarlines();
curmovt->fullbarend = TRUE;
}


/*************************************************
*              Caesurastyle                      *
*************************************************/

static void
caesurastyle(void)
{
movt_intproc();
if (curmovt->caesurastyle > 1)
  {
  error_moan(ERR37, "Number less than 2");
  curmovt->caesurastyle = 0;
  }
}


/*************************************************
*             Clef size (relative)               *
*************************************************/

static void
clefsize(void)
{
sigch();
if (isdigit(read_ch))
  {
  set_fontsize(oo(fontsizestr,fontsize_clefs), FALSE);
  (curmovt->fontsizes)->fontsize_clefs *= 10;
  }
else error_moan(ERR10, "Number");
}


/************************************************
*            Clef style                         *
************************************************/

static void
clefstyle(void)
{
movt_intproc();
if (curmovt->clefstyle > 3) error_moan(ERR10, "Number less than 4");
}


/************************************************
*             Clef widths                       *
************************************************/

static int shape[] = { clef_treble, clef_bass, clef_alto, clef_h, clef_none };

static void
clefwidths(void)
{
int i;
sigch();
for (i = 0; i < 5 && isdigit(read_ch); i++)
  {
  int width, j;
  (void)read_expect_integer(&width, FALSE, FALSE);
  if (read_ch == ',') next_ch();
  sigch();
  for (j = 0; j < clef_count; j++)
    if (main_cleftypes[j] == shape[i]) curmovt->clefwidths[j] = width;
  }
}


/*************************************************
*                Copyzero                        *
*************************************************/

static void
copyzero(void)
{
zcopystr **pp = &(curmovt->zcopy);
sigch();

if (!isdigit(read_ch)) { error_moan(ERR10, "stave number"); return; }

while (isdigit(read_ch))
  {
  zcopystr *p = store_Xget(sizeof(zcopystr));
  *pp = p;
  pp = &(p->next);
  p->next = NULL;
  p->baradjust = 0;
  p->stavenumber = read_integer(FALSE);
  if (read_ch == '/')
    {
    next_ch();
    if (!read_expect_integer(&(p->adjust), TRUE, TRUE)) break;
    }
  else p->adjust = 0;
  sigch();
  if (read_ch == ',') next_sigch();
  }
}


/*************************************************
*              Doublenotes                       *
*************************************************/

static void
doublenotes(void)
{
main_notenum *= 2;
curmovt->time = read_scaletime(curmovt->time);
}


/*************************************************
*               Gracespacing                     *
*************************************************/

/* Read up to two fixed point numbers, second defaulting to the first; "+" and
"-" can be used to adjust values. */

static void
gracespacing(void)
{
int arg;
int value = 0;
BOOL wasrelative = FALSE;

sigch();
if (read_ch == '+' || read_ch == '-')
  {
  value = curmovt->gracespacing[0];
  wasrelative = TRUE;
  }
if (!read_expect_integer(&arg, TRUE, TRUE)) return;
curmovt->gracespacing[0] = value + arg;

sigch();
if (read_ch == ',') next_ch();
sigch();
if (!isdigit(read_ch) && read_ch != '+' && read_ch != '-')
  {
  curmovt->gracespacing[1] = wasrelative?
    curmovt->gracespacing[1] + arg :
    curmovt->gracespacing[0];
  return;
  }

value = (read_ch == '+' || read_ch == '-')? curmovt->gracespacing[1] : 0;
if (!read_expect_integer(&arg, TRUE, TRUE)) return;
curmovt->gracespacing[1] = value + arg;
}


/*************************************************
*               Halvenotes                       *
*************************************************/

static void
halvenotes(void)
{
if (main_notenum > 1) main_notenum /= 2; else main_noteden *= 2;
curmovt->time = read_scaletime(curmovt->time);
}


/*************************************************
*            Hyphenstring                        *
*************************************************/

static void
hyphenstring(void)
{
if (read_plainstring()) curmovt->hyphenstring = store_copystring(read_word);
  else error_moan(ERR10, "String");
}


/*************************************************
*              Justify                           *
*************************************************/

static void
justify(void)
{
int yield = 0;
for (;;)
  {
  sigch();
  if (isalpha(read_ch))
    {
    uschar *backupptr = read_chptr;
    int  backup = read_ch;
    next_word();
    if (Ustrcmp(read_word, "top") == 0)         yield |= just_top;
    else if (Ustrcmp(read_word, "bottom") == 0) yield |= just_bottom;
    else if (Ustrcmp(read_word, "left") == 0)   yield |= just_left;
    else if (Ustrcmp(read_word, "right") == 0)  yield |= just_right;
    else if (Ustrcmp(read_word, "all") == 0)    yield |= just_all;
    else { read_chptr = backupptr; read_ch = backup; break; }
    }
  else break;
  }
curmovt->justify = yield;
}


/*************************************************
*                 Key                            *
*************************************************/

/* After setting the key for this movement, ensure the relevant stave data is
fudged, in case there are transposed text strings in the subsequent header
lines. The transpose value will already be fudged. */

static void
key(void)
{
curmovt->key = read_key();
stave_key = curmovt->key;
stave_key_tp = transpose_key(stave_key, stave_transpose);
}


/*************************************************
*                 Keytranspose                   *
*************************************************/

static void
keytranspose(void)
{
int i, oldkey;
keytransposestr *k;

oldkey = read_key();
if (oldkey == N_key)
  {
  error_moan(ERR10, "Transposable key signature");
  return;
  }

for (k = main_keytranspose; k != NULL; k = k->next)
  {
  if (k->oldkey == oldkey) break;
  }

if (k == NULL)
  {
  k = store_Xget(sizeof(keytransposestr));
  k->oldkey = oldkey;
  k->next = main_keytranspose;
  main_keytranspose = k;
  for (i = 0; i < 12; i++)
    {
    k->newkeys[i] = -1;
    k->letterchanges[i] = -1;
    }
  }

for (;;)
  {
  int sign = 1;
  next_sigch();
  if (read_ch == '-')
    {
    sign = -1;
    next_ch();
    }
  else if (!isdigit(read_ch)) break;

  i = read_integer(FALSE) * sign;
  while (i > 11) i -= 12;
  while (i < 0) i += 12;
  sigch();

  if (read_ch != '=')
    {
    error_moan(ERR10, "'='");
    goto ERROR;
    }
  next_ch();
  k->newkeys[i] = read_key();

  if (read_ch != '/')
    {
    error_moan(ERR10, "'/'");
    goto ERROR;
    }
  else
    {
    int x;
    next_ch();
    if (!read_expect_integer(&x, FALSE, TRUE)) goto ERROR;
    if (x < -6 || x > 6)
      {
      error_moan(ERR10, "Number in the range -6 to +6");
      goto ERROR;
      }
    if (abs(x) > i + 1)
      {
      error_moan(ERR146, x, i);
      goto ERROR;
      }
    k->letterchanges[i] = x;
    }
  }

return;

ERROR:
k->newkeys[i] = oldkey;
k->letterchanges[i] = 0;
return;
}



/*************************************************
*                 Landscape                      *
*************************************************/

static void
landscape(void)
{
int temp = curmovt->truelinelength;
curmovt->truelinelength = main_truepagelength;
main_truepagelength = temp;
if (opt_sheetsize == sheet_A5) main_truepagelength -= 28000;

temp = main_sheetwidth;
main_sheetwidth = main_sheetheight;
main_sheetheight = temp;
opt_landscape = TRUE;
if (main_lastmovement != 1) error_moan(ERR20);
}


/*************************************************
*                Layout                          *
*************************************************/

static void
layout(void)
{
int *temp = store_Xget(LAYOUT_MAXSIZE * 2 * sizeof(int));
int stack[20];
int ptr = 0;
int level = 0;

for (;;)
  {
  int value;
  sigch();
  if (!isdigit(read_ch))
    {
    if (ptr == 0 || level > 0) { error_moan(ERR10, "Number"); return; }

    /* Final item is always repeat back to start */

    temp[ptr++] = lv_repeatptr;
    temp[ptr++] = 0;

    /* Save in correct size piece of store */

    curmovt->layout = store_Xget(ptr*sizeof(int));
    memcpy(curmovt->layout, temp, ptr * sizeof(int));
    store_free(temp);
    return;
    }

  /* Value must be > 0 */

  read_expect_integer(&value, FALSE, FALSE);
  if (value == 0)
    {
    error_moan(ERR16, "Zero value changed to 1");
    value = 1;
    }

  /* If number followed by '(' it is a repeat count */

  sigch();
  if (read_ch == '(')
    {
    next_ch();
    temp[ptr++] = lv_repeatcount;
    temp[ptr++] = value;
    stack[level++] = ptr;
    }

  /* Else it is a barcount value, with varying terminators. If none of the
  specials, it does nothing, and another number will be a continuation, while
  anything else is the next directive. There may be a number of these
  terminators. */

  else
    {
    temp[ptr++] = lv_barcount;
    temp[ptr++] = value;

    for (;;)
      {
      if (read_ch == ',') { next_sigch(); break; }

      /* Close bracket is the end of a repeat. Check nesting. It can be followed
      by comma, semicolon, or another bracket. */

      else if (read_ch == ')')
        {
        if (level <= 0) { error_moan(ERR10, "Bracket not"); return; } else
          {
          temp[ptr++] = lv_repeatptr;
          temp[ptr++] = stack[--level];
          }
        next_sigch();
        }

      /* Semicolon generates a new page item */

      else if (read_ch == ';')
        {
        temp[ptr++] = lv_newpage;
        next_sigch();
        }

      /* Anything else, just carry on with the big loop */

      else break;
      }
    }
  }
}


/*************************************************
*               Ledgerstyle                      *
*************************************************/

/* The table sets pointers so the value goes into the curmovt->ledger variable.
Translate 0/1 into the old/new ledger characters. */

static void
ledgerstyle(void)
{
movt_intproc();
curmovt->ledger = (curmovt->ledger == 0)? '=' : 184;
}


/*************************************************
*             Makekey                            *
*************************************************/

/* Read the definition of a custom key signature. */

static void
makekey(void)
{
int i, n;

sigch();
if (toupper(read_ch) != 'X' ||
    (next_ch(), !read_expect_integer(&n, FALSE, FALSE)) ||
    n == 0 || n > MAX_XKEYS)
  {
  error_moan(ERR144, MAX_XKEYS);
  return;
  }

n--;  /* Now in range 0-9 */
for (i = 0; i < 10; i++) main_xkeys[n][i] = 0;  /* Reset all accidentals */

for (;;)
  {
  int ac;

  sigch();
  switch(read_ch)
    {
    case '#':
    next_ch();
    if (read_ch == '#')
      {
      ac = ac_dsharp;
      next_ch();
      }
    else
      {
      ac = ac_sharp;
      if (read_ch == '-')
        {
        ac |= 0x80;
        next_ch();
        }
      }
    break;

    case '$':
    next_ch();
    if (read_ch == '$')
      {
      ac = ac_dflat;
      next_ch();
      }
    else
      {
      ac = ac_flat;
      if (read_ch == '-')
        {
        ac |= 0x80;
        next_ch();
        }
      }
    break;

    case '%':
    ac = ac_natural;
    next_ch();
    break;

    default:
    return;
    }

  if (!read_expect_integer(&i, FALSE, FALSE)) return;
  if (i > 9)
    {
    error_moan(10, "Number between 0 and 9");
    return;
    }

  main_xkeys[n][i] = ac;
  }
}


/*************************************************
*             Maxbeamslope                       *
*************************************************/

static void
maxbeamslope(void)
{
if (!read_expect_integer(&curmovt->maxbeamslope1, TRUE, FALSE)) return;
if (!read_expect_integer(&curmovt->maxbeamslope2, TRUE, FALSE)) return;
}


/*************************************************
*              Midichannel                       *
*************************************************/

/* Local subroutine to make a copy of a map if this is the first time it's been
updated in this movement. */

static void
copy_midi_map(int flag, int size, uschar **anchor)
{
if ((read_headmap & flag) == 0)
  {
  uschar *new = store_Xget(size);
  memcpy(new, *anchor, size);
  *anchor = new;
  read_headmap |= flag;
  }
}

/* The main function */

static void
midichannel(void)
{
int channel;

debug_printf("midichannel start\n");

/* A channel number is always expected */

if (!read_expect_integer(&channel, FALSE, FALSE)) return;

if (channel < 1 || channel > MIDI_MAXCHANNEL)
  {
  error_moan(ERR109, "channel", MIDI_MAXCHANNEL);
  channel = 1;
  }

/* Deal with an optional voice setting */

if (read_plainstring())
  {
  int voicenumber;
  if (read_word[0] == 0) voicenumber = 129; else  /* => don't do MIDI voice setting */
    {
    if (read_word[0] == '#') voicenumber = Uatoi(read_word+1);
      else voicenumber = read_getmidinumber(midi_voicenames, read_word, US"voice");
    if (voicenumber < 1 || voicenumber > 128)
      {
      if (midi_filename != NULL) error_moan(ERR109, "voice", 128);
      voicenumber = 1;
      }
    }

  copy_midi_map(rh_midivoice, MIDI_MAXCHANNEL, &(curmovt->midi_voice));
  curmovt->midi_voice[channel-1] = voicenumber - 1;

  /* There may be an optional volume setting */

  if (read_ch == '/')
    {
    int vol;
    next_ch();
    if (read_expect_integer(&vol, FALSE, FALSE))
      {
      if (vol > 15) error_moan(ERR10, "Number between 0 and 15"); else
        {
        copy_midi_map(rh_midivolume, MIDI_MAXCHANNEL, &(curmovt->midi_volume));
        curmovt->midi_volume[channel-1] = vol;
        }
      }
    }
  }

/* Deal with an optional stave list */

sigch();
if (isdigit(read_ch))
  {
  int i;
  int pitch = 0;

  copy_midi_map(rh_midichannel, MAX_STAVE+1, &(curmovt->midi_channel));

  movt_mapproc();    /* read stave list into read_map */

  /* Deal with optional 'pitch' forcing */

  if (read_plainstring())
    {
    if (read_word[0] == 0) pitch = 0;  /* => don't do MIDI pitch forcing */
      else if (read_word[0] == '#') pitch = Uatoi(read_word+1);
        else pitch = read_getmidinumber(midi_percnames, read_word, US"percussion instrument");
    copy_midi_map(rh_midinote, MAX_STAVE+1, &(curmovt->midi_note));
    }

  /* Now update the per-stave data */

  for (i = 1; i <= MAX_STAVE; i++)
    {
    if (mac_teststave(read_map, i))
      {
      if (pitch) curmovt->midi_note[i] = pitch;
      curmovt->midi_channel[i] = channel;
      }
    }
  }

debug_printf("midichannel end\n");
}


/*************************************************
*               Midistart                        *
*************************************************/

static void
midistart(void)
{
int max = 0;
int count = 0;
int *temp = NULL;

for (;;)
  {
  int value;
  sigch();
  if (!isdigit(read_ch))
    {
    if (count == 0) { error_moan(ERR10, "Number"); return; }
    temp[0] = count;
    curmovt->midi_start = temp;
    return;
    }

  /* Get more store if needed */

  if (++count > max)
    {
    int newmax = max + 20;
    int *new = store_Xget((newmax + 1) * sizeof(int));
    if (max > 0)
      {
      memcpy(new, temp, (max+1) * sizeof(int));
      store_free(temp);
      }
    temp = new;
    max = newmax;
    }

  read_expect_integer(&value, FALSE, FALSE);
  temp[count] = value;
  }
}


/*************************************************
*               Notespacing                      *
*************************************************/

static void
notespacing(void)
{
int i;
sigch();
if (read_ch == '*')
  {
  int f;
  next_ch();
  if (!read_expect_integer(&f, TRUE, FALSE)) return;
  if (read_ch == '/')
    {
    int d;
    next_ch();
    if (!read_expect_integer(&d, TRUE, FALSE)) return;
    f = mac_fdiv(f, d);
    }
  for (i = 0; i < 8; i++)
    curmovt->notespacing[i] = (f * curmovt->notespacing[i])/1000;
  }

else
  {
  for (i = 0; i < 8; i++)
    {
    if (!read_expect_integer(main_notespacing+i, TRUE, FALSE)) return;
    curmovt->notespacing[i] = main_notespacing[i];
    sigch();
    if (read_ch == ',') next_ch();
    }
  }
}



/*************************************************
*            Oldstretchrule                      *
*************************************************/

/* This used to be a standard boolean, but it's now extended to an integer
option called "stretchrule". This is a backwards-compatibility function. */

static void
oldstretchrule(void)
{
opt_stretchrule = 0;
}


/*************************************************
*               Page                             *
*************************************************/

static void
page(void)
{
main_pageinc = 1;
if (main_lastmovement != 1) { error_moan(ERR20); return; }
if (!read_expect_integer(&main_firstpage, FALSE, FALSE)) return;
sigch();
if (!isdigit(read_ch)) return;
read_expect_integer(&main_pageinc, FALSE, FALSE);
}



/*************************************************
*               Playtempo                        *
*************************************************/

static void
playtempo(void)
{
movt_intproc();           /* Read a single number */

/* Now look for additional data giving tempo changes within a movement */

sigch();
if (read_ch == ',') next_sigch();

if (isdigit(read_ch))
  {
  BOOL barerror = FALSE;
  int ii;
  int i = 0;
  int lastbar = 0;
  int *copylist;
  int list[100];

  while (isdigit(read_ch))
    {
    int bar = read_integer(TRUE);
    int tempo;
    if (read_ch != '/')
      {
      error_moan(ERR10, "/");
      break;
      }
    next_ch();
    if (!read_expect_integer(&tempo, FALSE, FALSE)) break;

    if (bar <= lastbar)
      {
      if (!barerror) { error_moan(ERR86); barerror = TRUE; }
      }
    lastbar = bar;

    if (i > 99)
      {
      error_moan(ERR85, 50);
      break;
      }

    list[i++] = bar;
    list[i++] = tempo;

    sigch();
    if (read_ch == ',') next_sigch();
    }

  copylist = store_get((i+1) * sizeof(int));
  for (ii = 0; ii < i; ii++) copylist[ii] = list[ii];
  copylist[ii] = BIGNUMBER;
  curmovt->play_tempo_changes = copylist;
  }
}


/*************************************************
*              Playtranspose                     *
*************************************************/

static void
playtranspose(void)
{
sigch();
while (isdigit(read_ch))
  {
  int amount;
  int stave = read_integer(FALSE);
  sigch();

  if (read_ch != '/')
    {
    error_moan(ERR10, "/");
    return;
    }

  next_ch();
  if (!read_expect_integer(&amount, FALSE, TRUE)) return;
  sigch();
  (curmovt->playtranspose)[stave] = amount;
  if (read_ch == ',') next_ch();
  sigch();
  }
}


/*************************************************
*               Playvolume                       *
*************************************************/

static void
playvolume(void)
{
int i, v;
uschar *vv;

if (!read_expect_integer(&v, FALSE, FALSE)) return;  /* Default setting */

if (v > 15)
  {
  error_moan(ERR10, "Number between 0 and 15");
  return;
  }

curmovt->play_volume = vv = store_Xget(MAX_STAVE + 1);
for (i = 1; i <= MAX_STAVE; i++) vv[i] = v;

/* Now look for additional data giving stave volumes */

sigch();
if (read_ch == ',') next_sigch();

while (isdigit(read_ch))
  {
  int stave = read_integer(FALSE);
  if (read_ch != '/')
    {
    error_moan(ERR10, "/");
    break;
    }
  next_ch();
  if (!read_expect_integer(&v, FALSE, FALSE)) break;

  if (v > 15)
    {
    error_moan(ERR10, "Number between 0 and 15");
    break;
    }

  vv[stave] = v;
  sigch();
  if (read_ch == ',') next_sigch();
  }
}


/*************************************************
*             PMW version check                  *
*************************************************/

static void
pmwversion(void)
{
BOOL ok = FALSE;
int c = '=';
int v;

sigch();
if (read_ch == '>' || read_ch == '<' || read_ch == '=')
  {
  c = read_ch;
  next_ch();
  }
if (!read_expect_integer(&v, TRUE, FALSE)) return;

switch (c)
  {
  case '<': ok = version_fixed < v; break;
  case '=': ok = version_fixed == v; break;
  case '>': ok = version_fixed > v; break;
  }

if (!ok) error_moan(ERR28, c, v, version_fixed);
}


/*************************************************
*                Printtime                       *
*************************************************/

/* Local subroutine to deal with one string possibly followed by /s and a
number.

Arguments:
  s          pointer to where to put a pointer to the string
  offset     pointer to where to put the font offset

Returns:     TRUE if all goes well; FALSE on error
*/

static BOOL
ptstring(uschar **s, uschar *offset)
{
*s = string_check(string_read());

if (read_ch == '/')
  {
  int fo;
  next_ch();
  if (read_ch == 's')
    {
    next_ch();
    if (!read_expect_integer(&fo, FALSE, FALSE)) return FALSE;
    if ((fo -= 1) >= MaxFontSizes) error_moan(ERR39, MaxFontSizes);
    }
  else
    {
    error_moan(ERR10, "/s");
    return FALSE;
    }
  *offset = fo;
  }
else *offset = ff_offset_ts;

return TRUE;
}


/* The actual routine */

static void
printtime(void)
{
ptimestr *p = store_Xget(sizeof(ptimestr));
p->next = main_printtime;
p->movt_number = main_lastmovement;
main_printtime = p;
if ((p->time = read_time()) == 0) return;
if (!ptstring(&(p->top), &(p->offsettop))) return;
(void)ptstring(&(p->bot), &(p->offsetbot));
}


/*************************************************
*                Printkey                        *
*************************************************/

static void
printkey(void)
{
pkeystr *p = store_Xget(sizeof(pkeystr));
p->next = main_printkey;
p->movt_number = main_lastmovement;
main_printkey = p;
p->key = read_key();
p->clef = read_clef();
p->string = string_check(string_read());
sigch();
p->cstring = (read_ch == '"')? string_check(string_read()) : US"";
}


/*************************************************
*                 Pssetup                        *
*************************************************/

static void
pssetup(void)
{
if (main_lastmovement != 1) error_moan(ERR20); else
  {
  headstr *h = main_pssetup;
  headstr **hh = &main_pssetup;

  while (h != NULL)
    {
    hh = &(h->next);
    h = *hh;
    }

  if (read_plainstring())
    {
    h = store_Xget(sizeof(headstr));
    h->next = NULL;
    h->size = 0;
    h->text = store_copystring(read_word);
    h->spaceabove = 0;
    *hh = h;
    }

  else error_moan(ERR10, "String");
  }
}


/*************************************************
*              Rehearsalmarks                    *
*************************************************/

static void
rehearsalmarks(void)
{
sigch();

while (isalpha(read_ch))
  {
  next_word();
  sigch();
  if (Ustrcmp(read_word, "linestartleft") == 0 ||
      Ustrcmp(read_word, "nolinestartleft") == 0)
    {
    curmovt->rehearsallsleft = (read_word[0] == 'l');
    continue;
    }

  if (Ustrcmp(read_word, "boxed") == 0) curmovt->rehearsalstyle = text_box;
    else if (Ustrcmp(read_word, "ringed") == 0) curmovt->rehearsalstyle = text_ring;
      else if (Ustrcmp(read_word, "plain") == 0) curmovt->rehearsalstyle = 0;
        else error_moan(ERR10, "\"boxed\", \"ringed\", or \"plain\"");
  break;
  }

set_fontsize(oo(fontsizestr, fontsize_rehearse), TRUE);
set_fontname(oo(movtstr, font_rehearse));
}


/*************************************************
*        Sheetdepth and sheetwidth               *
*************************************************/

static void
sheetdim(void)
{
opt_sheetsize = sheet_unknown;
opt_intproc();
}


/*************************************************
*                 Sheetsize                      *
*************************************************/

static void
sheetsize(void)
{
next_word();
if (Ustrcmp(read_word, "a4") == 0 || Ustrcmp(read_word, "A4") == 0)
  {
  curmovt->truelinelength = 480000;
  main_truepagelength = 720000;
  main_sheetwidth = 595000;
  main_sheetheight = 842000;
  opt_sheetsize = sheet_A4;
  }
else if (Ustrcmp(read_word, "a3") == 0 || Ustrcmp(read_word, "A3") == 0)
  {
  curmovt->truelinelength = 730000;
  main_truepagelength = 1060000;
  main_sheetwidth = 842000;
  main_sheetheight = 1190000;
  opt_sheetsize = sheet_A3;
  }
else if (Ustrcmp(read_word, "a5") == 0 || Ustrcmp(read_word, "A5") == 0)
  {
  curmovt->truelinelength = 366000;
  main_truepagelength = 480000;
  main_sheetwidth = 421000;
  main_sheetheight = 595000;
  opt_sheetsize = sheet_A5;
  }
else if (Ustrcmp(read_word, "b5") == 0 || Ustrcmp(read_word, "B5") == 0)
  {
  curmovt->truelinelength = 420000;
  main_truepagelength = 590000;
  main_sheetwidth = 499000;
  main_sheetheight = 709000;
  opt_sheetsize = sheet_B5;
  }
else if (Ustrcmp(read_word, "letter") == 0)
  {
  curmovt->truelinelength = 500000;
  main_truepagelength = 670000;
  main_sheetwidth = 612000;
  main_sheetheight = 792000;
  opt_sheetsize = sheet_letter;
  }
else error_moan(ERR10, "\"A3\", \"A4\", \"A5\", \"B5\", or \"letter\"");
if (main_lastmovement != 1) error_moan(ERR20);
}


/*************************************************
*               Startbracketbar                  *
*************************************************/

static void
startbracketbar(void)
{
sigch();
if (isalpha(read_ch))
  {
  next_word();
  if (Ustrcmp(read_word, "join") == 0) curmovt->startjoin = TRUE;
    else if (Ustrcmp(read_word, "nojoin") == 0) curmovt->startjoin = FALSE;
      else { error_moan(ERR10, "\"join\" or \"nojoin\""); return; }
  }

read_expect_integer(&curmovt->startbracketbar, FALSE, FALSE);
}


/*************************************************
*               Stavesize                        *
*************************************************/

static void
stavesize(void)
{
int *stavesizes = store_Xget((MAX_STAVE+1)*sizeof(int));
memcpy(stavesizes, curmovt->stavesizes, (MAX_STAVE+1)*sizeof(int));
curmovt->stavesizes = stavesizes;

sigch();
while (isdigit(read_ch))
  {
  int size;
  int stave = read_integer(FALSE);
  if (stave > MAX_STAVE) { error_moan(ERR22, MAX_STAVE+1); stave = 1; }
  if (read_ch != '/') { error_moan(ERR10, "/"); return; }
  next_ch();
  if (!read_expect_integer(&size, TRUE, FALSE)) return;
  stavesizes[stave] = size;
  sigch();
  if (read_ch == ',') next_sigch();
  }
}


/*************************************************
*                Stavespacing                    *
*************************************************/

static void
stavespacing(void)
{
BOOL first = TRUE;
int i;
int *newsp = store_Xget((MAX_STAVE+1) * sizeof(int));
int *newen = store_Xget((MAX_STAVE+1) * sizeof(int));
usint done[STAVE_BITVEC_SIZE];

newsp[0] = newen[0] = 0;
mac_initstave(done, 0);

for (i = 1; i <= MAX_STAVE; i++)
  {
  newsp[i] = 44000;
  newen[i] = 0;
  }

sigch();
while (isdigit(read_ch))
  {
  int space;
  int ensure = 0;
  int stave = read_integer(FALSE);

  sigch();

  if (read_ch != '/')
    {
    if (first)
      {
      space = stave*1000;
      if (read_ch == '.') space += read_integer(TRUE);
      for (i = 1; i <= MAX_STAVE; i++) newsp[i] = space;
      goto NEXT;
      }
    else
      {
      error_moan(ERR10, "/");
      return;
      }
    }

  next_ch();
  if (!read_expect_integer(&space, TRUE, FALSE)) return;
  sigch();
  if (read_ch == '/')
    {
    ensure = space;
    next_ch();
    if (!read_expect_integer(&space, TRUE, FALSE)) return;
    }

  if (stave == 0) error_moan(ERR107); else
    {
    if (mac_teststave(done, stave)) error_moan(ERR106, stave, US"stavespacing");
    mac_setstave(done, stave);
    newsp[stave] = space;
    newen[stave] = ensure;
    }

NEXT:

  if (read_ch == ',') next_ch();
  sigch();
  first = FALSE;
  }

curmovt->stave_spacing = newsp;
curmovt->stave_ensure = newen;
}


/*************************************************
*               Startlinespacing                 *
*************************************************/

static void
startlinespacing(void)
{
startlinestr *p = store_Xget(sizeof(startlinestr));
p->clefspace = p->keyspace = p->timespace = p->notespace = 0;

sigch();
if (isdigit(read_ch) || read_ch == '-' || read_ch == '+')
  {
  (void)read_expect_integer(&(p->clefspace), TRUE, TRUE);
  if (read_ch == ',') next_ch();
  sigch();
  }
if (isdigit(read_ch) || read_ch == '-' || read_ch == '+')
  {
  (void)read_expect_integer(&(p->keyspace), TRUE, TRUE);
  if (read_ch == ',') next_ch();
  sigch();
  }
if (isdigit(read_ch) || read_ch == '-' || read_ch == '+')
  {
  (void)read_expect_integer(&(p->timespace), TRUE, TRUE);
  if (read_ch == ',') next_ch();
  sigch();
  }
if (isdigit(read_ch) || read_ch == '-' || read_ch == '+')
  {
  (void)read_expect_integer(&(p->notespace), TRUE, TRUE);
  }

curmovt->startline = p;
}


/*************************************************
*                Stemswap                        *
*************************************************/

static void
stemswap(void)
{
next_word();
if (Ustrcmp(read_word, "up") == 0) curmovt->stemswaptype = stemswap_up;
else if (Ustrcmp(read_word, "down") == 0) curmovt->stemswaptype = stemswap_down;
else if (Ustrcmp(read_word, "left") == 0) curmovt->stemswaptype = stemswap_left;
else if (Ustrcmp(read_word, "right") == 0) curmovt->stemswaptype = stemswap_right;
else error_moan(ERR10, "\"up\", \"down\", \"left\", or \"right\"");
}


/*************************************************
*              Stemswaplevel                     *
*************************************************/

static void
stemswaplevel(void)
{
int i, x;
int *new = store_Xget((MAX_STAVE+1) * sizeof(int));
for (i = 0; i <= MAX_STAVE; i++) new[i] = P_3L;

if (!read_expect_integer(&x, FALSE, TRUE)) return;

if (x > 0 && read_ch == '/')
  {
  for (;;)
    {
    int y;
    next_ch();
    if (!read_expect_integer(&y, FALSE, TRUE)) return;
    new[x] = P_3L + y*2;
    if (read_ch == ',') next_ch();
    sigch();
    if (!isdigit(read_ch)) break;
    x = read_integer(FALSE);
    if (read_ch != '/') { error_moan(ERR10, "/"); return; }
    }
  }

else for (i = 0; i <= MAX_STAVE; i++) new[i] = P_3L + x*2;

curmovt->stemswaplevel = new;
}


/************************************************
*            Stem lengths                       *
************************************************/

static void
stemlengths(void)
{
int i;
sigch();
for (i = 2; i < 8 && isdigit(read_ch); i++)
  {
  (void)read_expect_integer(&(curmovt->tailadjusts[i]), TRUE, TRUE);
  if (read_ch == ',') next_ch();
  sigch();
  }
}


/*************************************************
*                Textfont                        *
*************************************************/

/* This also handles musicfont, if arg1 is non-zero. */

static void
textfont(void)
{
BOOL include = FALSE;
int fontid, n;

/* Font selection is permitted only in the first movement */

if (main_lastmovement != 1) { error_moan(ERR20); return; }

/* If font id given (a music font), use it, else read a font word or "extra
<n>", optionally followed by "include". */

if (read_dir->arg1 != 0) fontid = read_dir->arg1; else
  {
  if ((fontid = font_fontword(FALSE)) < 0) return;
  sigch();
  if (read_ch != '"')
    {
    uschar *save_chptr = read_chptr;
    next_word();
    if (Ustrcmp(read_word, "include") == 0) include = TRUE;
      else read_chptr = save_chptr;
    }
  }

/* Read the first font name, which must be present, and the
optional second name for the PostScript font. */

if (!read_plainstring()) { error_moan(ERR10, "String"); return; }
if (read_ch == ',') next_ch();

/* See if is already in the font list. If it is, we set its number in the font
table, enable include if required, and return. If not, we set it up as a new
font. */

n = font_search(read_word);
if (n >= 0)
  {
  font_table[fontid] = n;
  if (include) font_List[n].include = TRUE;
  }
else
  {
  fontstr *fs;
  if (font_count >= MAX_FONTS) { error_moan(ERR26, MAX_FONTS); return; }
  fs = &(font_List[font_count]);
  fs->psname = store_copystring(read_word);
  fs->widths = NULL;
  fs->high_tree = NULL;
  fs->utr = NULL;
  fs->utrcount = 0;
  fs->heights = NULL;
  fs->kerns = NULL;
  fs->kerncount = -1;
  fs->stdencoding = fs->fixedpitch = fs->hasfi = FALSE;
  fs->include = include;
  font_table[fontid] = font_count;
  font_loadtables(font_count++);
  }

/* Ensure both music fonts are the same */

if (fontid == font_mf) font_table[font_mu] = font_table[font_mf];
}


/*************************************************
*            Textsizes                           *
*************************************************/

/* Note: set_fontsize() does nothing if the next character is not a digit. */

static void
textsizes(void)
{
int i;
for (i = 0; i < MaxFontSizes; i++)
  {
  set_fontsize(oo(fontsizestr,fontsize_text) + i*sizeof(int), TRUE);
  if (read_ch == ',') next_ch();
  }
sigch();
if (isdigit(read_ch))
  {
  error_moan(ERR143, MaxFontSizes);
  while (isdigit(read_ch) || isspace(read_ch) || read_ch == ',') next_ch();
  }
}


/*************************************************
*                     Time                       *
*************************************************/

static void
timeproc(void)
{
int t = read_time();            /* returns 0 after giving error */
if (t != 0) curmovt->time = t;
}



/*************************************************
*                Transpose                       *
*************************************************/

static void
transpose(void)
{
int temp = curmovt->transpose;
if (temp == no_transpose) temp = 0;
movt_intproc();
curmovt->transpose += temp;
if (abs(curmovt->transpose) > max_transpose)
  error_moan(ERR139, (temp == 0)? "T":"Accumulated t", curmovt->transpose,
    max_transpose);  /* Hard error */

/* In case this is followed by heading/footing lines that contain transposed
text, fudge up some fake stave values. The stave_key will already be set. */

stave_transpose = curmovt->transpose;
stave_key_tp = transpose_key(stave_key, stave_transpose);
}


/*************************************************
*               Transposed key                   *
*************************************************/

static void
transposedkey(void)
{
int oldkey = read_key();
next_word();
if (Ustrcmp(read_word, "use") != 0) error_moan(ERR10, "\"use\""); else
  {
  int newkey = read_key();
  trkeystr *k = store_Xget(sizeof(trkeystr));
  k->oldkey = oldkey;
  k->newkey = newkey;
  k->next = main_transposedkeys;
  main_transposedkeys = k;
  }
}



/*************************************************
*             Transposed accidental option       *
*************************************************/

static void
transposedacc(void)
{
next_word();
if (Ustrcmp(read_word, "force") == 0) main_transposedaccforce = TRUE; else
  if (Ustrcmp(read_word, "noforce") == 0) main_transposedaccforce = FALSE;
    else error_moan(ERR10, "\"force\" or \"noforce\"");
}


/*************************************************
*             Trill string                       *
*************************************************/

static void
trillstring(void)
{
sigch();
if (isdigit(read_ch)) set_fontsize(oo(fontsizestr,fontsize_trill), TRUE);
if (read_plainstring()) curmovt->trillstring = store_copystring(read_word);
else error_moan(ERR10, "String");
}



/*************************************************
*            Heading directive list              *
*************************************************/

/* The function for reading a draw list is held separately for convenience. */

static dirstr read_headlist[] = {
  { "accadjusts",       accadjusts,     0, 0 },
  { "accspacing",       accspacing,     0, 0 },
  { "b2pffont",         b2pffont,       0, 0 },
  { "bar",              movt_intproc,   oo(movtstr,baroffset), int_u+int_less1 },
  { "barcount",         movt_intproc,   oo(movtstr,maxbarcount),  int_u },
  { "barlinesize",      movt_intproc,   oo(movtstr,barlinesize), int_u+int_f },
  { "barlinespace",     barlinespace,   oo(movtstr,barlinespace), int_u+int_f },
  { "barlinestyle",     movt_intproc,   oo(movtstr,barlinestyle), int_u },
  { "barnumberlevel",   barnumberlevel, oo(movtstr,barno_level), int_rs+int_f },
  { "barnumbers",       barnumbers,     0, 0 },
  { "beamendrests",     movt_cboolproc, oo(movtstr,beamendrests), TRUE },
  { "beamflaglength",   movt_intproc,   oo(movtstr,beamflaglength), int_u+int_f },
  { "beamthickness",    movt_intproc,   oo(movtstr,beamdepth), int_u+int_f },
  { "bottommargin",     movt_intproc,   oo(movtstr,botmargin), int_u+int_f },
  { "brace",            movt_listproc,  oo(movtstr,bracelist), 0 },
  { "bracestyle",       bracestyle,     oo(movtstr,bracestyle), int_u },
  { "bracket",          movt_listproc,  oo(movtstr,bracketlist), 0 },
  { "breakbarlines",    breakbarlines,  oo(movtstr,breakbarlines), -1 },
  { "breakbarlinesx",   breakbarlinesx, oo(movtstr,breakbarlines), -1 },
  { "breveledgerextra", movt_intproc,   oo(movtstr,breveledgerextra),int_u+int_f},
  { "breverests",       movt_cboolproc, oo(movtstr,breverests), TRUE },
  { "caesurastyle",     caesurastyle,   oo(movtstr,caesurastyle), int_u },
  { "check",            movt_cboolproc, oo(movtstr,check), TRUE },
  { "checkdoublebars",  movt_cboolproc, oo(movtstr,checkdoublebars), TRUE },
  { "clefsize",         clefsize,       0, 0 },
  { "clefstyle",        clefstyle,      oo(movtstr,clefstyle), int_u },
  { "clefwidths",       clefwidths,     0, 0 },
  { "codemultirests",   movt_cboolproc, oo(movtstr,codemultirests), TRUE },
  { "copyzero",         copyzero,       0, 0 },
  { "cuegracesize",     movt_fszproc,   oo(fontsizestr,fontsize_cuegrace), FALSE },
  { "cuesize",          movt_fszproc,   oo(fontsizestr,fontsize_cue), FALSE },
  { "dotspacefactor",   movt_intproc,   oo(movtstr,dotspacefactor), int_u+int_f },
  { "doublenotes",      doublenotes,    0, 0 },
  { "draw",             read_draw,      0, 0 },
  { "endlinesluradjust",movt_intproc,   oo(movtstr,endlinesluradjust), int_f },
  { "endlineslurstyle", movt_intproc,   oo(movtstr,endlineslurstyle), int_u },
  { "endlinetieadjust", movt_intproc,   oo(movtstr,endlinetieadjust), int_f },
  { "endlinetiestyle",  movt_intproc,   oo(movtstr,endlinetiestyle), int_u },
  { "extenderlevel",    movt_intproc,   oo(movtstr,extenderlevel), int_f },
  { "fbsize",           movt_fszproc,   oo(fontsizestr,fontsize_text)+ff_offset_fbass*sizeof(int), TRUE },
  { "footing",          movt_headproc,  oo(movtstr,footing), rh_footing },
  { "footnotesep",      movt_intproc,   oo(movtstr,footnotesep), int_f },
  { "footnotesize",     movt_fszproc,   oo(fontsizestr,fontsize_footnote), TRUE },
  { "gracesize",        movt_fszproc,   oo(fontsizestr,fontsize_grace), FALSE },
  { "gracespacing",     gracespacing,   0, 0 },
  { "gracestyle",       movt_intproc,   oo(movtstr,gracestyle), int_u },
  { "hairpinlinewidth", movt_intproc,   oo(movtstr,hairpinlinewidth), int_rs+int_f },
  { "hairpinwidth",     movt_intproc,   oo(movtstr,hairpinwidth), int_rs+int_f },
  { "halfflatstyle",    movt_intproc,   oo(movtstr,hflatstyle), int_u },
  { "halfsharpstyle",   movt_intproc,   oo(movtstr,hsharpstyle), int_u },
  { "halvenotes",       halvenotes,     0, 0 },
  { "heading",          movt_headproc,  oo(movtstr,heading), rh_heading },
  { "hyphenstring",     hyphenstring,   0, 0 },
  { "hyphenthreshold",  movt_intproc,   oo(movtstr,hyphenthreshold), int_rs+int_f },
  { "join",             movt_listproc,  oo(movtstr,joinlist), 0 },
  { "joindotted",       movt_listproc,  oo(movtstr,joindottedlist), 0 },
  { "justify",          justify,        0, 0 },
  { "key",              key,            0, 0 },
  { "keydoublebar",     movt_cboolproc, oo(movtstr,keydoublebar), TRUE },
  { "keysinglebar",     movt_cboolproc, oo(movtstr,keydoublebar), FALSE },
  { "keytranspose",     keytranspose,   0, 0 },
  { "keywarn",          movt_cboolproc, oo(movtstr,keywarn), TRUE },
  { "landscape",        landscape,      0, 0 },
  { "lastfooting",      movt_headproc,  oo(movtstr,lastfooting), rh_lastfooting },
  { "layout",           layout,         0, 0 },
  { "ledgerstyle",      ledgerstyle,    oo(movtstr,ledger), int_u },
  { "leftmargin",       movt_intproc,   oo(movtstr,leftmargin), int_f },
  { "linelength",       movt_intproc,   oo(movtstr,truelinelength), int_rs+int_f },
  { "longrestfont",     movt_fontproc,  oo(movtstr,font_longrest), oo(fontsizestr, fontsize_restct) },
  { "magnification",    opt_intproc,    glob_magnification, int_u+int_f },
  { "makekey",          makekey,        0, 0 },
  { "maxbeamslope",     maxbeamslope,   0, 0 },
  { "maxvertjustify",   opt_intproc,    glob_maxvertjustify, int_u+int_f },
  { "midichannel",      midichannel,    0, 0 },
  { "midifornotesoff",  opt_boolproc,   glob_midifornotesoff, TRUE },
  { "midistart",        midistart,      0, 0 },
  { "miditempo",        playtempo,      oo(movtstr,play_tempo), int_u },
  { "miditranspose",    playtranspose,  0, 0 },
  { "midivolume",       playvolume,     0, 0 },
  { "midkeyspacing",    movt_intproc,   oo(movtstr,keyspacing), int_f },
  { "midtimespacing",   movt_intproc,   oo(movtstr,timespacing), int_f },
  { "musicfont",        textfont,       font_mf, 0 },
  { "nobeamendrests",   movt_cboolproc, oo(movtstr,beamendrests), FALSE },
  { "nocheck",          movt_cboolproc, oo(movtstr,check), FALSE },
  { "nocheckdoublebars",movt_cboolproc, oo(movtstr,checkdoublebars), FALSE },
  { "nocodemultirests", movt_cboolproc, oo(movtstr,codemultirests), FALSE },
  { "nokerning",        opt_boolproc,   glob_kerning, FALSE },
  { "nokeywarn",        movt_cboolproc, oo(movtstr,keywarn), FALSE },
  { "nosluroverwarnings", movt_cboolproc, oo(movtstr,tiesoverwarnings), FALSE },
  { "nospreadunderlay", movt_cboolproc, oo(movtstr,spreadunderlay), FALSE },
  { "notespacing",      notespacing,    0, 0 },
  { "notime",           movt_cboolproc, oo(movtstr,showtime), FALSE },
  { "notimebase",       movt_cboolproc, oo(movtstr,showtimebase), FALSE },
  { "notimewarn",       movt_cboolproc, oo(movtstr,timewarn), FALSE },
  { "nounderlayextenders",movt_cboolproc, oo(movtstr,underlayextenders), FALSE },
  { "oldbeambreak",     opt_boolproc,   glob_oldbeambreak, TRUE },
  { "oldrestlevel",     opt_boolproc,   glob_oldrestlevel, TRUE },
  { "oldstemlength",    opt_boolproc,   glob_oldstemlength, TRUE },
  { "oldstretchrule",   oldstretchrule, 0, 0 },
  { "overlaydepth",     movt_intproc,   oo(movtstr,overlaydepth), int_f },
  { "overlaysize",      movt_fszproc,   oo(fontsizestr,fontsize_text)+ff_offset_olay*sizeof(int), TRUE },
  { "page",             page,           0, 0 },
  { "pagefooting",      movt_headproc,  oo(movtstr,pagefooting), rh_pagefooting },
  { "pageheading",      movt_headproc,  oo(movtstr,pageheading), rh_pageheading },
  { "pagelength",       opt_intproc,    glob_pagelength, int_rs+int_f },
  { "playtempo",        playtempo,      oo(movtstr,play_tempo), int_u },
  { "playtranspose",    playtranspose,  0, 0 },
  { "playvolume",       playvolume,     0, 0 },
  { "pmwversion",       pmwversion,     0, 0 },
  { "printkey",         printkey,       0, 0 },
  { "printtime",        printtime,      0, 0 },
  { "psfooting",        movt_headproc,  oo(movtstr,footing), rh_footing+rh_ps },
  { "psheading",        movt_headproc,  oo(movtstr,heading), rh_heading+rh_ps },
  { "pslastfooting",    movt_headproc,  oo(movtstr,lastfooting), rh_lastfooting+rh_ps },
  { "pspagefooting",    movt_headproc,  oo(movtstr,pagefooting), rh_pagefooting+rh_ps },
  { "pspageheading",    movt_headproc,  oo(movtstr,pageheading), rh_pageheading+rh_ps },
  { "pssetup",          pssetup,        0, 0 },
  { "rehearsalmarks",   rehearsalmarks, 0, 0 },
  { "repeatbarfont",    movt_fontproc,  oo(movtstr,font_repeat), oo(fontsizestr, fontsize_repno) },
  { "repeatstyle",      movt_intproc,   oo(movtstr,repeatstyle), int_u },
  { "righttoleft",      opt_boolproc,   glob_righttoleft, TRUE },
  { "selectstaff",      movt_mapproc,   oo(movtstr,staves), 0 },
  { "selectstave",      movt_mapproc,   oo(movtstr,staves), 0 },
  { "selectstaves",     movt_mapproc,   oo(movtstr,staves), 0 },
  { "sheetdepth",       sheetdim,       glob_sheetdepth, int_u+int_f },
  { "sheetsize",        sheetsize,      0, 0 },
  { "sheetwidth",       sheetdim,       glob_sheetwidth, int_u+int_f },
  { "shortenstems",     movt_intproc,   oo(movtstr,shorten), int_u+int_f },
  { "sluroverwarnings", movt_cboolproc, oo(movtstr,tiesoverwarnings), TRUE },
  { "smallcapsize",     movt_intproc,   oo(movtstr,smallcapsize), int_u+int_f },
  { "staffsize",        stavesize,      0, 0 },
  { "staffsizes",       stavesize,      0, 0 },
  { "staffspacing",     stavespacing,   0, 0 },
  { "startbracketbar",  startbracketbar, 0, 0 },
  { "startlinespacing", startlinespacing, 0, 0 },
  { "startnotime",      movt_cboolproc, oo(movtstr,startnotime), TRUE},
  { "stavesize",        stavesize,      0, 0 },
  { "stavesizes",       stavesize,      0, 0 },
  { "stavespacing",     stavespacing,   0, 0 },
  { "stemlengths",      stemlengths,    0, 0 },
  { "stemswap",         stemswap,       0, 0 },
  { "stemswaplevel",    stemswaplevel,  0, 0 },
  { "stretchrule",      opt_intproc,    glob_stretchrule, int_u },
  { "suspend",          movt_mapproc,   oo(movtstr,suspend), -1 },
  { "systemgap",        movt_intproc,   oo(movtstr,systemgap), int_u+int_f },
  { "textfont",         textfont,       0, 0 },
  { "textsize",         textsizes,      0, 0 },
  { "textsizes",        textsizes,      0, 0 },
  { "thinbracket",      movt_listproc,  oo(movtstr,thinbracketlist), 0 },
  { "time",             timeproc,       0, 0 },
  { "timebase",         movt_cboolproc, oo(movtstr,showtimebase), TRUE },
  { "timefont",         movt_fontproc,  oo(movtstr,font_time),oo(fontsizestr,fontsize_text)+ff_offset_ts*sizeof(int) },
  { "timewarn",         movt_cboolproc, oo(movtstr,timewarn), TRUE },
  { "topmargin",        movt_intproc,   oo(movtstr,topmargin), int_u+int_f },
  { "transpose",        transpose,      oo(movtstr,transpose), int_s },
  { "transposedacc",    transposedacc,  0, 0 },
  { "transposedkey",    transposedkey,  0, 0 },
  { "trillstring",      trillstring,    0, 0 },
  { "tripletfont",      movt_fontproc,  oo(movtstr,font_triplet),oo(fontsizestr,fontsize_triplet) },
  { "tripletlinewidth", movt_intproc,   oo(movtstr,tripletlinewidth), int_u+int_f },
  { "underlaydepth",    movt_intproc,   oo(movtstr,underlaydepth), int_f },
  { "underlayextenders",movt_cboolproc, oo(movtstr,underlayextenders), TRUE },
  { "underlaysize",     movt_fszproc,   oo(fontsizestr,fontsize_text)+ff_offset_ulay*sizeof(int), TRUE },
  { "underlaystyle",    movt_intproc,   oo(movtstr,underlaystyle), int_u },
  { "unfinished",       movt_cboolproc, oo(movtstr,unfinished), TRUE },
  { "vertaccsize",      movt_fszproc,   oo(fontsizestr,fontsize_vertacc), FALSE }
};

static int read_headsize = sizeof(read_headlist)/sizeof(dirstr);



/*************************************************
*              Read next heading directive       *
*************************************************/

/*
Arguments:    none
Returns:      nothing
*/

void
next_heading(void)
{
dirstr *first, *last;

sigch();
if (read_ch == EOF) return;
next_word();

/* A null word is either the end of the heading, or an error */

if (read_word[0] == 0)
  {
  if (read_ch != '[') error_moan(ERR10, "Heading directive");
  return;
  }

/* Look up the word in the list of heading directives and if found, call the
appropriate routine. */

first = read_headlist;
last  = first + read_headsize;

while (last > first)
  {
  int c;
  read_dir = first + (last-first)/2;
  c = Ustrcmp(read_word, read_dir->name);
  if (c == 0)
    {
    (read_dir->proc)();
    return;
    }
  if (c > 0) first = read_dir + 1; else last = read_dir;
  }
error_moan(ERR19, read_word);
}

/* End of read2.c */
