chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mup / charinfo.c
diff --git a/mup/mup/charinfo.c b/mup/mup/charinfo.c
new file mode 100644 (file)
index 0000000..7f010f4
--- /dev/null
@@ -0,0 +1,3081 @@
+
+/* Copyright (c) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 by Arkkra Enterprises */
+/* All rights reserved */
+
+/* functions related to characters: information about the size and shape of
+ * characters to be printed, to initialize the internal tables
+ * to tell how big each character is, etc.
+ */
+
+#include <string.h>
+#include "defines.h"
+#include "structs.h"
+#include "globals.h"
+
+
+/* code for invalid music character */
+#define BAD_CHAR       '\377'
+/* code for invalid number in user input string */
+#define BAD_NUMBER     -30000
+
+/* machine-generated sorted list for translating
+ * music character names to internal code numbers. */
+/* The +1 is because there is an "end-of-list" entry with charname == 0 */
+extern struct SPECCHAR Mus_char_table[NUM_MFONTS][CHARS_IN_FONT+1];
+#ifdef EXTCHAR
+extern struct SPECCHAR Ext_char_table[];
+#endif
+
+
+/* save information about characters in string as we go, in order to be
+ * able to backspace back over them */
+struct BACKSPACEINFO {
+       char    code;
+       char    font;
+};
+
+
+#ifndef __STDC__
+extern char *bsearch();        /* binary search library function */
+#endif
+extern long strtol();
+
+/* static functions */
+static char *get_font P((char *string, int *font_p, int prev_font, char *fname,
+               int lineno));
+static char *get_num P((char *string, int *num_p));
+static int sc_compare P((const void *item1_p, const void *item2_p));
+#ifdef EXTCHAR
+static unsigned char ext_name2num P((char *name));
+#endif
+static int starts_piled P((char *string, int *font_p, int *size_p,
+               char **pile_start_p_p));
+static int str_cmd P((char *str, int *size_p, int *in_pile_p));
+static int get_accidental P((unsigned char *str, char *accidental_p,
+               int *acc_size_p, int trans_natural, int *escaped_p));
+static int add_accidental P((char *buff, int acc_character, int acc_size,
+               int escaped));
+static int dim_tri P((unsigned char *str, char *replacement,
+               int size, int is_chord));
+static int smallsize P((int size));
+static int accsize P((int size));
+\f
+
+/* return the height (in inches) of a character of specified font and size */
+
+double
+height(font, size, ch)
+
+int font;
+int size;
+int ch;                /* which character */
+
+{
+       int chval;
+
+       chval = ch & 0xff;
+
+       /* control characters have no height */
+       if (chval < FIRST_CHAR) {
+               return(0.0);
+       }
+
+       return((Fontinfo[ font_index(font) ].ch_height[ CHAR_INDEX(chval) ] 
+               / FONTFACTOR) * ((float)size / (float)DFLT_SIZE) );
+}
+\f
+
+/* return the width (in inches) of a character of specified font and size */
+
+double
+width(font, size, ch)
+
+int font;
+int size;
+int ch;                /* which character */
+
+{
+       int chval;
+
+       chval = ch & 0xff;
+
+       /* control characters have no width */
+       if (chval < FIRST_CHAR) {
+               return(0.0);
+       }
+
+       return((Fontinfo[ font_index(font) ].ch_width[ CHAR_INDEX(chval) ] 
+               / FONTFACTOR) * ((float)size / (float)DFLT_SIZE) );
+}
+\f
+
+/* return the ascent (in inches) of a character of specified font and size */
+
+double
+ascent(font, size, ch)
+
+int font;
+int size;
+int ch;                /* which character */
+
+{
+       int chval;
+
+       chval = ch & 0xff;
+
+       /* control characters have no ascent */
+       if (chval < FIRST_CHAR) {
+               return(0.0);
+       }
+
+       return((Fontinfo[ font_index(font) ].ch_ascent[ CHAR_INDEX(chval) ]
+               / FONTFACTOR) * ((float) size / (float)DFLT_SIZE) );
+}
+\f
+
+/* return the descent (in inches) of a character of specified font and size */
+
+double
+descent(font, size, ch)
+
+int font;
+int size;
+int ch;                /* which character */
+
+{
+       return ( height(font, size, ch) - ascent(font, size, ch) );
+}
+\f
+
+/* given a user input string, normalize it. This means:
+ * Put the default font in [0] and default size in [1] of the string.
+ * Change backslashed things to internal format. Each starts with a
+ * hyper-ASCII code byte and is followed by one or more data bytes.
+ * Note that in all cases in internal format is no longer than the
+ * incoming format.
+ * Change any \f(XX) to          STR_FONT font_number
+ * Change any \s(NN) to   STR_SIZE actual_size
+ *     Note that NN might have a sign to indicate relative size change.
+ * Change any \v(NN) to   STR_VERTICAL vertical_offset
+ *     Note that NN might have a sign to indicate direction,
+ *     negative means downward.
+ * Change any \/ to      STR_SLASH
+ * Change any \| to       STR_L_ALIGN (piled mode only)
+ * Change any \^ to       STR_C_ALIGN (piled mode only)
+ * Change any backslashed space to space when in piled mode
+ * Change any space to newline while in piled mode
+ * Change \(xxxx) to      STR_MUS_CHAR size mus_char_code
+ * Change \% to           STR_PAGENUM %
+ * Change \# to                  STR_NUMPAGES #
+ * Change \n to newline
+ * Change \b to           STR_BACKSPACE n
+ *      where n is how much to back up for the
+ *     default size, in BACKSP_FACTORths of an inch
+ * Change backslashed backslash or double quote to just be themselves.
+ * Reject any other control characters or illegal backslash escapes.
+ * The string is null-terminated.
+ *
+ * The normalized string is put back into the original string buffer
+ * that was passed in, and a pointer to it is returned.
+ *
+ * Note that some functions in lyrics.c, and prntdata.c
+ * also have knowledge of the escape conventions,
+ * so if these change, check there too. But it is intended
+ * that all the rest of the code gets at strings indirectly
+ * via functions in this file, so the details can be hidden here.
+ */
+
+char *
+fix_string(string, font, size, fname, lineno)
+
+char *string;  /* original string */
+int font;      /* default font for string */
+int size;      /* default size for string */
+char *fname;   /* file name, for error messages */
+int lineno;    /* input line number, for error messages */
+
+{
+       char *tmpbuff;                  /* for normalized string */
+       int leng;                       /* strlen(string) + 1 */
+       char *inp_p, *out_p;            /* walk thru orig & normalized string */
+       int nsize;                      /* new size */
+       int prevsize;                   /* previous size */
+       int msize;                      /* size for music character */
+       int vert;                       /* argument to \v without sign */
+       int vertval = 0;                /* signed argument to \v */
+       int has_vertical = NO;          /* YES if \v or pile found */
+       int has_newline = NO;           /* YES if \n somewhere in string */
+       int pile_mode = NO;
+       int align_points = 0;           /* how many aligments points found */
+       int error;                      /* YES if have found an error */
+       char spec_name[100], *sc_p;     /* name of special music character, or
+                                        * extended character set character */
+       unsigned char extchar;          /* value for extended character */
+       unsigned char muschar;          /* value for music character */
+       int now_font;                   /* current font */
+       int newfont;                    /* proposed new font */
+       int prevfont;                   /* previous font */
+       int mfont;                      /* music font */
+       struct BACKSPACEINFO *backspaceinfo;    /* font/size for backspacing */
+       int backspaceindex = 0;         /* index into backspaceinfo */
+       float backup;                   /* backspace distance in inches
+                                        * for default size */
+       int backupval;                  /* value to store for backspace
+                                        * distance */
+
+
+       /* fill in default font and size */
+       string[0] = (char) font;
+       if (rangecheck(size, MINSIZE, MAXSIZE, "size") == NO) {
+               size = MAXSIZE;
+       }
+       string[1] = (char) size;
+       now_font = prevfont = font;
+       prevsize = size;
+
+       leng = strlen(string) + 1;
+       MALLOCA(char, tmpbuff, leng);
+       MALLOC(BACKSPACEINFO, backspaceinfo, leng);
+       /* walk through incoming string, creating normalized string */
+       for (error = NO, out_p = tmpbuff + 2, inp_p = string + 2;
+                                       (error == NO) && (*inp_p != '\0');
+                                       inp_p++, out_p++) {
+
+               /* handle backslashed stuff */
+               if (*inp_p == '\\') {
+
+                       /* skip past the backslash */
+                       inp_p++;        
+
+                       switch( *inp_p) {
+
+                       case '\n':
+                               /* ignore the backslashed newline */
+                               out_p--;
+                               break;
+
+                       case '\r':
+                               if (*(inp_p) == '\n') {
+                                       inp_p++;
+                               }
+                               out_p--;
+                               break;
+
+                       case 'f':
+                               /* font change */
+                               inp_p = get_font(++inp_p, &newfont, prevfont,
+                                                       fname, lineno);
+                               if (newfont == FONT_UNKNOWN) {
+                                       error = YES;
+                               }
+                               else {
+                                       *out_p++ = (char) STR_FONT;
+                                       *out_p = (char) newfont;
+                                       prevfont = now_font;
+                                       now_font = newfont;
+                               }
+                               break;
+
+                       case 's':
+                               /* size change */
+                               if (*++inp_p == '(') {
+                                       switch (*++inp_p) {
+                                       case '+':
+                                               inp_p = get_num(++inp_p, &nsize);
+                                               if (nsize > 0) {
+                                                       nsize += size;
+                                               }
+                                               break;
+                                       case '-':
+                                               inp_p = get_num(++inp_p, &nsize);
+                                               if (nsize > 0) {
+                                                       nsize = size - nsize;
+                                               }
+                                               break;
+                                       case 'P':
+                                               if (strncmp(inp_p, "PV)", 3) == 0) {
+                                                       nsize = prevsize;
+                                                       inp_p += 2;
+                                               }
+                                               else {
+                                                       nsize = BAD_NUMBER;
+                                               }
+                                               break;
+                                       case 'p':
+                                               if (strncmp(inp_p, "previous)", 9) == 0) {
+                                                       nsize = prevsize;
+                                                       inp_p += 8;
+                                               }
+                                               else {
+                                                       nsize = BAD_NUMBER;
+                                               }
+                                               break;
+                                       default:
+                                               inp_p = get_num(inp_p, &nsize);
+                                               break;
+                                       }
+                               }
+                               else {
+                                       nsize = BAD_NUMBER;
+                               }
+
+                               /* if got valid size, store it */
+                               if (nsize == BAD_NUMBER) {
+                                       l_yyerror(fname, lineno,
+                                               "Invalid format for size value");
+                                       error = YES;
+                               }
+                               else if (rangecheck(nsize, MINSIZE,
+                                               MAXSIZE, "size") == YES) {
+                                       *out_p++ = (char) STR_SIZE;
+                                       *out_p = (char) nsize;
+                                       /* save new size */
+                                       prevsize = size;
+                                       size = nsize;
+                               }
+                               else {
+                                       error = YES;
+                               }
+
+                               break;
+
+                       case 'v':
+                               /* vertical motion */
+                               if (*++inp_p == '(') {
+                                       switch (*++inp_p) {
+                                       case '-':
+                                               inp_p = get_num(++inp_p, &vert);
+                                               if (vert >= 0) {
+                                                       vertval = -vert;
+                                               }
+                                               break;
+
+                                       case '+':
+                                               ++inp_p;
+                                               /* fall through */
+                                       default:
+                                               inp_p = get_num(inp_p, &vert);
+                                               if (vert >= 0) {
+                                                       vertval = vert;
+                                               }
+                                               break;
+                                       }
+                               }
+                               else {
+                                       vert = BAD_NUMBER;
+                               }
+
+                               if (vert == BAD_NUMBER) {
+                                       l_yyerror(fname, lineno,
+                                               "Invalid format for vertical motion value");
+                                       error = YES;
+                               }
+                               else if (rangecheck(vertval, -100, 100,
+                                               "vertical") == YES) {
+                                       /* if motion is zero, don't even bother
+                                        * to save it, else do */
+                                       if (vertval != 0) {
+                                               /* convert percentage to 
+                                                * STR_VERTICAL units */
+                                               if (vertval > 0) {
+                                                       vertval = vertval *
+                                                               MAXVERTICAL/100;
+                                               }
+                                               else {
+                                                       vertval = -vertval *
+                                                               MINVERTICAL/100;
+                                               }
+                                               *out_p++ = (char) STR_VERTICAL;
+                                               *out_p = (char) ENCODE_VERT(
+                                                       vertval );
+                                       }
+                               }
+                               else {
+                                       error = YES;
+                               }
+
+                               /* we don't allow backspacing to something
+                                * before a vertical motion--this is almost
+                                * like a newline. */
+                               backspaceindex = 0;
+
+                               has_vertical = YES;
+                       
+                               break;
+                               
+                       case ':':
+                               /* If this begins a pile, and the next thing
+                                * in input ends the pile, just ignore them
+                                * both to keep things simpler later. */
+                               if (pile_mode == NO && *(inp_p+1) == '\\'
+                                                       && *(inp_p+2) == ':') {
+                                       inp_p += 2;
+                                       /* no output character */
+                                       out_p--;
+                               }
+                               else {
+                                       *out_p = (char) STR_PILE;
+                                       has_vertical = YES;
+                                       pile_mode = (pile_mode == YES ? NO : YES);
+                               }
+                               align_points = 0;
+                               break;
+
+                       case '|':
+                       case '^':
+                               if (pile_mode == NO) {
+                                       l_yyerror(fname, lineno,
+                                               "alignment point only allowed in piled mode");
+                                       *out_p =  *inp_p;
+                               }
+
+                               else if (++align_points > 1) {
+                                       l_yyerror(fname, lineno,
+                                               "only one alignment point allowed per line");
+                                       *out_p =  *inp_p;
+                               }
+
+                               else if (*inp_p == '^') {
+                                       int next_ch;
+                                       *out_p = (char) STR_C_ALIGN;
+                                       next_ch = *(inp_p+1) & 0xff;
+                                       /* it's too much trouble to handle
+                                        * things like font changes between
+                                        * the \^ and the character that
+                                        * will be allowed, so disallow them,
+                                        * since user can easily put them
+                                        * before the \^ anyway. */
+                                       if ( (IS_STR_COMMAND(next_ch)
+                                               && next_ch != STR_MUS_CHAR)
+                                               || *(inp_p+1) == ' '
+                                               || iscntrl(*(inp_p+1)) ) {
+                                          l_yyerror(fname, lineno,
+                                               "\\^ must be followed by normal character");
+                                       }
+                               }
+                               else {
+                                       *out_p = (char) STR_L_ALIGN;
+                               }
+                               has_vertical = YES;
+                               break;
+
+                       case ' ':
+                               if (pile_mode == NO) {
+                                       l_yyerror(fname, lineno,
+                                               "backslashed space only allowed in piled mode");
+                               }
+                               *out_p = ' ';
+                               break;
+
+                       case '/':
+                               /* This is only allowed after one
+                                * or more digits */
+                               if ( inp_p - string < 4 ||
+                                               ! isdigit( *(inp_p - 2)) ) {
+                                       l_yyerror(fname, lineno,
+                                               "slash can only be used after digit(s)");
+                               }
+                               *out_p = (char) STR_SLASH;
+                               break;
+                       case '\\':
+                       case '"':
+                               /* real backslash or embedded quote, copy it */
+                               backspaceinfo[backspaceindex].code = *inp_p;
+                               backspaceinfo[backspaceindex++].font
+                                                       = (char) now_font;
+                               *out_p = *inp_p;
+                               break;
+
+                       case '(':
+                               /* special music character or extended
+                                * character set character */
+                               /* make copy of name */
+                               for ( sc_p = spec_name, inp_p++;
+                                               *inp_p != ')' && *inp_p != '\0';
+                                               sc_p++, inp_p++) {
+                                       *sc_p = *inp_p;
+                               }
+                               *sc_p = '\0';
+
+#ifdef EXTCHAR
+                               /* first see if it is a character in the
+                                * extended character set */
+                               if ((extchar = ext_name2num(spec_name))
+                                                       != (unsigned char) BAD_CHAR) {
+                                       /* temporarily change to the extended
+                                        * character set font that corresponds
+                                        * to the current normal ASCII font,
+                                        * and output the extended character
+                                        * set code for the desired character.
+                                        * Then go back to original font */
+                                       *out_p++ = (char) STR_FONT;
+                                       *out_p++ = (char)
+                                               (now_font + EXT_FONT_OFFSET);
+                                       *out_p++ = extchar;
+                                       *out_p++ = (char) STR_FONT;
+                                       *out_p = (char) now_font;
+                                       backspaceinfo[backspaceindex].code
+                                                               = extchar;
+                                       backspaceinfo[backspaceindex++].font
+                                               = now_font + EXT_FONT_OFFSET;
+
+                                       /* mark that this extended character
+                                        * set font has been used */
+                                       Font_used[now_font + EXT_FONT_OFFSET] = YES;
+
+                                       break;
+                               }
+#endif
+                               /* look up music character with this name */
+                               msize = size;
+                               if ((muschar = mc_name2num(spec_name, fname,
+                                               lineno, &msize, &mfont))
+                                               != (unsigned char) BAD_CHAR) {
+                                       *out_p++ = (char) mfont2str(mfont);
+                                       *out_p++ = (char) msize;
+                                       *out_p = muschar;
+                                       backspaceinfo[backspaceindex].code
+                                                               = muschar;
+                                       backspaceinfo[backspaceindex++].font
+                                                               = FONT_MUSIC;
+                               }
+                               break;
+
+                       case '[':
+                               /* start of boxed text. We only allow this at
+                                * the beginning of a string */
+                               if (inp_p != string + 3) {
+                                       l_yyerror(fname, lineno,
+                                               "\\[ only allowed at beginning of string");
+                                       error = YES;
+                               }
+                               else {
+                                       *out_p = (char) STR_BOX;
+                               }
+                               break;
+
+                       case ']':
+                               /* end of boxed text. Only allowed at end of
+                                * string, and only if the string began
+                                * with a box start. */
+                               if (*(inp_p + 1) != '\0') {
+                                       l_yyerror(fname, lineno,
+                                               "\\] only allowed at end of string");
+                                       error = YES;
+                               }
+                               else if (IS_BOXED(tmpbuff) == NO) {
+                                       l_yyerror(fname, lineno,
+                                               "no matching \\[ for \\]");
+                                       error = YES;
+                               }
+                               else {
+                                       *out_p = (char) STR_BOX_END;
+                               }
+                               break;
+
+                       case '{':
+                               /* start of circled text. We only allow this at
+                                * the beginning of a string */
+                               if (inp_p != string + 3) {
+                                       l_yyerror(fname, lineno,
+                                               "\\{ only allowed at beginning of string");
+                                       error = YES;
+                               }
+                               else {
+                                       *out_p = (char) STR_CIR;
+                               }
+                               break;
+
+                       case '}':
+                               /* end of circled text. Only allowed at end of
+                                * string, and only if the string began
+                                * with a circle start. */
+                               if (*(inp_p + 1) != '\0') {
+                                       l_yyerror(fname, lineno,
+                                               "\\} only allowed at end of string");
+                                       error = YES;
+                               }
+                               else if (IS_CIRCLED(tmpbuff) == NO) {
+                                       l_yyerror(fname, lineno,
+                                               "no matching \\{ for \\}");
+                                       error = YES;
+                               }
+                               else {
+                                       *out_p = (char) STR_CIR_END;
+                               }
+                               break;
+
+                       case '%':
+                               /* too hard to deal with inside a pile... */
+                               if (pile_mode == YES) {
+                                       l_yyerror(fname, lineno,
+                                               "\\%c not allowed inside a pile\n", '%');
+                               }
+
+                               /* page number -- change to STR_PAGENUM-% */
+                               *out_p++ = (char) STR_PAGENUM;
+                               *out_p = '%';
+                               /* we really don't know at this point how far
+                                * to backspace over pagenum because we don't
+                                * know yet how many digits it is, etc, so we
+                                * punt and just use the % character
+                                * for width */
+                               backspaceinfo[backspaceindex].code = '%';
+                               backspaceinfo[backspaceindex++].font
+                                                       = (char) now_font;
+                               break;
+
+                       case '#':
+                               /* code basically the same as for % */
+                               if (pile_mode == YES) {
+                                       l_yyerror(fname, lineno,
+                                               "\\# not allowed inside a pile\n");
+                               }
+
+                               /* number of pages -- change to STR_NUMPAGES-# */
+                               *out_p++ = (char) STR_NUMPAGES;
+                               *out_p = '#';
+                               /* We really don't know at this point how far
+                                * to backspace, because we don't know yet
+                                * how many digits it is, etc, so we punt
+                                * and just use the # character for width. */
+                               backspaceinfo[backspaceindex].code = '#';
+                               backspaceinfo[backspaceindex++].font
+                                                       = (char) now_font;
+                               break;
+
+                       case 'n':
+                               /* newline */
+                               *out_p = '\n';
+                               /* can't back up to previous line */
+                               backspaceindex = 0;
+                               has_newline = YES;
+                               break;
+
+                       case 'b':
+                               /* can't back up past beginning of string */
+                               if (backspaceindex == 0) {
+                                       if (has_newline == YES || has_vertical == YES) {
+                                               l_yyerror(fname, lineno,
+                                                       "can't backspace before newline or vertical motion");
+                                       }
+                                       else {
+                                               l_yyerror(fname, lineno,
+                                                       "can't backspace before beginning of line");
+                                       }
+                                       error = YES;
+                               }
+                               else {
+                                       backspaceindex--;
+                                       backup = width(backspaceinfo
+                                                       [backspaceindex].font,
+                                                       DFLT_SIZE, backspaceinfo
+                                                       [backspaceindex].code);
+                                       *out_p++ = (char) STR_BACKSPACE;
+                                       /* calculate backup value to store */
+                                       backupval = (int) (backup * BACKSP_FACTOR);
+                                       if (backupval < 1) {
+                                               backupval = 1;
+                                       }
+                                       else if (backupval > 127) {
+                                               backupval = 127;
+                                       }
+                                       *out_p = (char) backupval;
+                               }
+                               break;
+
+                       default:
+                               yyerror("illegal \\ escape");
+                               error = YES;
+                               break;
+                       }
+               }
+
+               else if (iscntrl(*inp_p) ) {
+                       if (*inp_p == '\n') {
+                               backspaceindex = 0;
+                               has_newline = YES;
+                               *out_p = *inp_p;
+                       }
+                       else if (*inp_p == '\r' && *(inp_p+1) == '\n') {
+                               /* ignore DOS's extraneous \r */
+                               out_p--;
+                       }
+                       else {
+                               /* We don't support any other control
+                                * characters, but just convert others to
+                                * space and continue. That way user at least
+                                * gets something. Tab is something user may
+                                * expect to work, so we give a more clear
+                                * and specific error for that.
+                                */
+                               l_warning(fname, lineno,
+                                       "unsupported control character '\\0%o' %sin string replaced with space",
+                                       *inp_p, *inp_p =='\t' ? "(tab) ": "");
+                               *out_p = ' ';
+                       }
+               }
+               else if  (pile_mode == YES && *inp_p == ' ') {
+                       /* in piled mode, space means move down for next
+                        * item in pile. */
+                       *out_p = '\n';
+                       
+                       align_points = 0;
+                       backspaceindex = 0;
+               }
+               else {
+                       /* normal character -- copy as is */
+                       *out_p = *inp_p;
+                       backspaceinfo[backspaceindex].code = *inp_p;
+                       backspaceinfo[backspaceindex++].font = (char) now_font;
+               }
+       }
+       /* If we got an error, we would not have put anything into the
+        * final output position before incrementing out_p in the 'for' loop,
+        * so compensate, so we don't leave a garbage character. */
+       if (error == YES) {
+               out_p--;
+       }
+       *out_p = '\0';
+
+       if (error == NO && IS_BOXED(tmpbuff) == YES &&
+                               (*(out_p - 1) & 0xff) != (STR_BOX_END & 0xff)) {
+               l_yyerror(fname, lineno, "no matching \\] for \\[");
+       }
+
+       if (error == NO && IS_CIRCLED(tmpbuff) == YES &&
+                               (*(out_p - 1) & 0xff) != (STR_CIR_END & 0xff)) {
+               l_yyerror(fname, lineno, "no matching \\} for \\{");
+       }
+
+       /* to keep things simple, we don't allow
+        * mixing newlines with vertical motion */
+       if (has_vertical == YES && has_newline == YES) {
+               l_yyerror(fname, lineno,
+                       "can't have newline in same string with vertical motion or alignment");
+       }
+
+       /* now copy normalized string back onto original */
+       (void) strcpy(string + 2, tmpbuff + 2);
+       FREE(tmpbuff);
+       FREE(backspaceinfo);
+       return(string);
+}
+\f
+
+/* given pointer into a string, read a font name exclosed in parentheses.
+ * Return the corresponding font number, or
+ * FONT_UNKNOWN if name is invalid. Return pointer to last character
+ * processed in string */
+
+static char *
+get_font(string, font_p, prev_font, fname, lineno)
+
+char *string;  /* get font from this string */
+int *font_p;   /* return new font via this pointer */
+int prev_font; /* previous font */
+char *fname;   /* file name for errors */
+int lineno;    /* line number, for error messages */
+
+{
+       char fontname[BUFSIZ];
+       int font = FONT_UNKNOWN;
+       char *endparen;         /* where ')' is in string */
+       int length;             /* of font name */
+
+
+       if (*string == '(') {
+               string++;
+               if ((endparen = strchr(string, ')')) != (char *) 0) {
+                       length = endparen - string;
+                       (void) strncpy(fontname, string, (unsigned) length);
+                       fontname[length] = '\0';
+                       string += length;
+                       if (strcmp(fontname, "PV") == 0
+                                       || strcmp(fontname, "previous") == 0) {
+                               /* special case of "previous" font */
+                               font = prev_font;
+                       }
+                       else {
+                               font = lookup_font(fontname);
+                       }
+               }
+       }
+
+       *font_p = font;
+       if (font == FONT_UNKNOWN) {
+               l_yyerror(fname, lineno, "unknown font specified");
+       }
+       return(string);
+}
+\f
+
+/* given a pointer into a string, get a number followed by close parenthesis.
+ * Return the number via pointer, or BAD_NUMBER on error.
+ * Return pointer to the last character processed
+ * in the incoming string */
+
+static char *
+get_num(string, num_p)
+
+char *string;  /* get number from this string */
+int *num_p;    /* return number via this pointer, or -1 on error */
+
+{
+       if (isdigit(*string)) {
+               *num_p = strtol(string, &string, 10);
+               if (*string != ')') {
+                       *num_p = BAD_NUMBER;
+               }
+       }
+       else {
+               *num_p = BAD_NUMBER;
+       }
+       return(string);
+}
+\f
+
+/* compare the charname fields of 2 SPECCHAR structs and return
+ * their proper order, for comparison by bsearch() */
+
+static int
+sc_compare(item1_p, item2_p)
+
+#ifdef __STDC__
+const void *item1_p;   /* there are really struct SPECCHAR *, but bsearch
+                        * passes them as char * and we have to
+                        * cast appropriately */
+const void *item2_p;
+#else
+char *item1_p; /* there are really struct SPECCHAR *, but bsearch passes them
+                * as char * and we have to cast appropriately */
+char *item2_p;
+#endif
+
+{
+       return(strcmp( ((struct SPECCHAR *)(item1_p))->charname,
+                               ((struct SPECCHAR *)(item2_p))->charname));
+}
+\f
+
+/* given the name of a music character, return its code number.
+ * If the name is not a valid name, return BAD_CHAR.
+ * Just do a binary search in the name-to-code translation table.
+ */
+
+unsigned char
+mc_name2num(name, fname, lineno, size_p, font_p)
+
+char *name;    /* name for a music character */
+char *fname;   /* file name for error messages */
+int lineno;    /* input line number for error messages */
+int *size_p;   /* points to current size, proper size for music character
+                * is returned through here */
+int *font_p;   /* FONT_MUSIC* is returned here */
+
+{
+       struct SPECCHAR *info_p;/* translation entry for the given name */
+       struct SPECCHAR key;    /* what to look for */
+       int f;                          /* font index */
+       static unsigned int numch[NUM_MFONTS];  /* how many items in font */
+
+
+       /* first time through, find size of name-to-code table */
+       if (numch[0] == 0) {
+               for (f = 0; f < NUM_MFONTS; f++) {
+                       for ( ; Mus_char_table[f][numch[f]].charname != (char *)0;
+                                                       (numch[f])++) {
+                               ;
+                       }
+               }
+       }
+
+       /* check for "small" characters */
+       if (name[0] == 's' && name[1] == 'm') {
+               key.charname = name + 2;
+               *size_p = smallsize(*size_p);
+       }
+       else {
+               key.charname = name;
+       }
+
+       /* do binary search for code */
+       for (f = 0; f < NUM_MFONTS; f++) {
+               if ((info_p = (struct SPECCHAR *) bsearch((char *) &key, Mus_char_table[f],
+                               numch[f], sizeof(struct SPECCHAR), sc_compare))
+                               != (struct SPECCHAR *) 0) {
+                       *font_p = FONT_MUSIC + f;
+                       return( (unsigned char) info_p->code);
+               }
+       }
+
+       l_yyerror(fname, lineno, "unknown music character '%s'", name);
+       *font_p = FONT_MUSIC;
+       return( (unsigned char) BAD_CHAR);
+}
+#ifdef EXTCHAR
+\f
+
+/* given the name of an extended character set character,
+ * return its code number.
+ * If the name is not a valid name, return BAD_CHAR.
+ * Just do a binary search in the name-to-code translation table.
+ */
+
+static unsigned char
+ext_name2num(name)
+
+char *name;    /* name for an extended character set character */
+
+{
+       struct SPECCHAR *info_p;/* translation entry for the given name */
+       struct SPECCHAR key;    /* what to look for */
+       static unsigned int numch = 0;  /* how many items in xlation table */
+       char shortcut[12];      /* full name of shortcutted character */
+
+
+       /* find size of name-to-code table */
+       if (numch == 0) {
+               for (   ; Ext_char_table[numch].charname != (char *) 0;
+                                                       numch++) {
+                       ;
+               }
+       }
+
+       key.charname = name;
+
+       /* allow some shortcuts for common diacritical marks. A letter
+        * followed by one of '`^~:/,vo represents acute, grave, circumflex,
+        * tilde, dieresis, slash, cedilla, caron, and ring.
+        * And as a special case, ss represents germandbls */
+       if (strlen(name) == 2 && isalpha(name[0])) {
+               switch (name[1]) {
+               case '\'':
+                       (void) sprintf(shortcut, "%cacute", name[0]);
+                       key.charname = shortcut;
+                       break;
+               case '`':
+                       (void) sprintf(shortcut, "%cgrave", name[0]);
+                       key.charname = shortcut;
+                       break;
+               case '^':
+                       (void) sprintf(shortcut, "%ccircumflex", name[0]);
+                       key.charname = shortcut;
+                       break;
+               case '~':
+                       (void) sprintf(shortcut, "%ctilde", name[0]);
+                       key.charname = shortcut;
+                       break;
+               case ':':
+                       (void) sprintf(shortcut, "%cdieresis", name[0]);
+                       key.charname = shortcut;
+                       break;
+               case '/':
+                       (void) sprintf(shortcut, "%cslash", name[0]);
+                       key.charname = shortcut;
+                       break;
+               case ',':
+                       (void) sprintf(shortcut, "%ccedilla", name[0]);
+                       key.charname = shortcut;
+                       break;
+               case 'v':
+                       (void) sprintf(shortcut, "%ccaron", name[0]);
+                       key.charname = shortcut;
+                       break;
+               case 'o':
+                       (void) sprintf(shortcut, "%cring", name[0]);
+                       key.charname = shortcut;
+                       break;
+               case 's':
+                       if (name[0] == 's') {
+                               (void) sprintf(shortcut, "germandbls");
+                               key.charname = shortcut;
+                       }
+                       break;
+               default:
+                       /* not a special shortcut, leave as is */
+                       break;
+               }
+       }
+       /* Some more special case shortcuts: `` and '' are shortcuts for
+        * quotedblleft and quotedblright, and << and >> for guillemots */
+       if (strcmp(name, "``") == 0) {
+               key.charname = "quotedblleft";
+       }
+       else if (strcmp(name, "''") == 0) {
+               key.charname = "quotedblright";
+       }
+       else if (strcmp(name, "<<") == 0) {
+               key.charname = "guillemotleft";
+       }
+       else if (strcmp(name, ">>") == 0) {
+               key.charname = "guillemotright";
+       }
+
+       /* do binary search for code */
+       if ((info_p = (struct SPECCHAR *) bsearch((char *) &key, Ext_char_table,
+                       numch, sizeof(struct SPECCHAR), sc_compare))
+                       != (struct SPECCHAR *) 0) {
+               return( (unsigned char) info_p->code);
+       }
+
+       else {
+               /* don't do error message here, because it could just be a
+                * music character rather than an extended character set char */
+               return( (unsigned char) BAD_CHAR);
+       } 
+}
+#endif
+\f
+
+/* given the C_XXX code value for a music character, return the
+ * user name for the character. The first time this function gets
+ * called it sets up a translation array. Then it can just look up
+ * the name by using the code as an index into the array */
+
+char *
+mc_num2name(code, font)
+
+int code;      /* the code for the music character */
+int font;      /* FONT_MUSIC*   */
+
+{
+       static int xlate_tbl[NUM_MFONTS][CHARS_IN_FONT + FIRST_CHAR];
+                                       /* translate music char #define
+                                        * values to offset in Mus_char_table
+                                        * array */
+       int f;                          /* font index */
+       static int called = NO;         /* boolean, YES if this function
+                                        * has been called before */
+       register int numch;             /* how many music characters to do */
+
+
+       if (called == NO) {
+               called = YES;
+               /* first time. need to build table */
+
+               /* For each item in the Mus_char_table, fill in the
+                * element of the xlate_tbl array with its offset,
+                * or fill in -1 if no valid character with that code. */
+               for (f = 0; f < NUM_MFONTS; f++) {
+                       for ( numch = 0; numch < CHARS_IN_FONT + FIRST_CHAR; numch++) {
+                               xlate_tbl[f][numch] = -1;
+                       }
+               }
+               for (f = 0; f < NUM_MFONTS; f++) {
+                       for ( numch = 0; numch < CHARS_IN_FONT + FIRST_CHAR; numch++) {
+                               if (Mus_char_table[f][numch].charname != 0) {
+                                       xlate_tbl [f] [ Mus_char_table[f][numch].code & 0xff ] =
+                                       numch;
+                               }
+                               else {
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       /* now we just look up the name */
+       if ((numch = xlate_tbl[font - FONT_MUSIC][code & 0xff]) < 0) {
+               pfatal("bad music character [%d][%d] in mc_num2name",
+                                               font - FONT_MUSIC, code & 0xff);
+       }
+
+       return( Mus_char_table[font - FONT_MUSIC][numch].charname );
+}
+#ifdef EXTCHAR
+\f
+
+/* given the C_XXX code value for an extended character set char, return the
+ * user name for the character. The first time this function gets
+ * called it sets up a translation array. Then it can just look up
+ * the name by using the code as an index into the array */
+
+char *
+ext_num2name(code)
+
+int code;      /* the code for the extended character set character */
+
+{
+       static int xlate_tbl[CHARS_IN_FONT + FIRST_CHAR];
+                                       /* translate extended char
+                                        * #define values to offset in
+                                        * Ext_char_table array */
+       static int called = NO;         /* boolean, YES if this function
+                                        * has been called before */
+       register int numch;             /* how many extended characters to do */
+
+
+       if (called == NO) {
+               called = YES;
+               /* first time. need to build table */
+
+               /* initialize table to have nothing set */
+               for ( numch = 0; numch < CHARS_IN_FONT + FIRST_CHAR; numch++) {
+                       xlate_tbl[numch] = -1;
+               }
+
+               /* for each item in the Ext_char_table, fill in the
+                * element of the xlate_tbl array with its offset */
+               for (numch = 0; Ext_char_table[numch].charname != (char *) 0;
+                                                       numch++) {
+                       xlate_tbl [ Ext_char_table[numch].code & 0xff ] =
+                                       numch;
+               }
+       }
+
+       /* now we just look up the name */
+       if ((numch = xlate_tbl[code & 0xff]) < 0) {
+               pfatal("bad extended character set character (%d) in ext_num2name", code & 0xff);
+       }
+
+       return( Ext_char_table[numch].charname );
+}
+#endif
+\f
+
+/* return YES if string passed in consists solely of a music symbol, otherwise
+ * return NO */
+
+int
+is_music_symbol(str)
+
+char *str;             /* which string to check */
+
+{
+       char *string;
+       int font;
+       int size;
+
+
+       if (str == (char *) 0) {
+               return(NO);
+       }
+
+       font = str[0];
+       size = str[1];
+       string = str + 2;
+
+       /* has to be music char followed by null to be YES */
+       if (next_str_char(&string, &font, &size) == '\0') {
+               return(NO);
+       }
+       if ( ! IS_MUSIC_FONT(font)) {
+               return(NO);
+       }
+       if (next_str_char(&string, &font, &size) == '\0') {
+               return(YES);
+       }
+       return(NO);
+}
+\f
+
+/* return the ascent of a string in inches. This is the largest ascent of any
+ * character in the string */
+
+double
+strascent(str)
+
+char *str;     /* which string to process */
+
+{
+       float max_ascent, a;    /* tallest and current ascent */
+       char *s;                /* to walk through string */
+       int font, size, code;
+       int textfont;
+       double vertical, horizontal;
+       float baseline_offset;  /* to account for vertical motion */
+       int in_pile;
+       int only_mus_sym;       /* YES if string consists solely
+                                * of a music char */
+
+
+       if (str == (char *) 0) {
+               return(0.0);
+       }
+
+       only_mus_sym = is_music_symbol(str);
+
+       /* first 2 bytes are font and size. */
+       font = str[0];
+       size = str[1];
+
+       /* Walk through the string. */
+       for (max_ascent = 0.0, baseline_offset = 0.0, s = str + 2;
+                       (code = nxt_str_char(&s, &font, &size, &textfont,
+                       &vertical, &horizontal, &in_pile, NO)) > 0;    ) {
+
+               /* A newline goes to following line, so we probably won't
+                * get any higher ascent than we have so far, but if
+                * user gives enough vertical motion, we might, so continue. */
+               if (code == '\n') {
+                       baseline_offset -= fontheight(font, size);
+               }
+
+               /* adjust for any vertical motion */
+               if (vertical != 0.0) {
+                       baseline_offset += vertical;
+               }
+
+               /* music characters inside strings get moved up to the baseline,
+                * so use their height as ascent.
+                * Regular characters use the
+                * ascent of the character */
+               if ((IS_MUSIC_FONT(font))  && (only_mus_sym == NO)) {
+                       a = height(font, size, code);
+               }
+               else {
+                       a = ascent(font, size, code);
+               }
+               a += baseline_offset;
+
+               /* if tallest seen save this height */
+               if (a > max_ascent) {
+                       max_ascent = a;
+               }
+       }
+
+       /* if boxed, allow space for that */
+       if (IS_BOXED(str) == YES) {
+               max_ascent += 2.5 * STDPAD;
+       }
+       /* similarly, allow space for circle */
+       if (IS_CIRCLED(str) == YES) {
+               float ascent_adjust;
+               max_ascent += circled_dimensions(str, (float *) 0, (float *) 0,
+                                       &ascent_adjust, (float *) 0);
+               max_ascent += ascent_adjust;
+       }
+       return(max_ascent);
+}
+\f
+
+/* return the descent of a string in inches. This is the largest descent of any
+ * character in the string */
+
+double
+strdescent(str)
+
+char *str;     /* which string to process */
+
+{
+       float max_descent, d;   /* largest and current descent */
+       float line_descent;     /* descent caused by newlines */
+       double vertical, horizontal;
+       int in_pile;
+       char *s;                /* to walk through string */
+       int font, size, code;
+       int textfont;
+       int only_mus_sym;       /* YES if string consists solely
+                                * of a music char */
+
+
+       if (str == (char *) 0) {
+               return(0.0);
+       }
+
+       only_mus_sym = is_music_symbol(str);
+
+       /* first 2 bytes are font and size. */
+       font = str[0];
+       size = str[1];
+
+       /* walk through the string. */
+       for (max_descent = line_descent = 0.0, s = str + 2;
+                       (code = nxt_str_char(&s, &font, &size, &textfont,
+                       &vertical, &horizontal, &in_pile, NO)) > 0
+                       || vertical != 0.0;  ) {
+
+               /* Adjust for vertical motion. Since line_descent is
+                * measured downward and vertical is upward, have to
+                * substract the vertical, then adjust max_descent
+                * to compensate. */
+               if (vertical != 0.0) {
+                       line_descent -= vertical;
+                       max_descent += vertical;
+                       if (code == 0) {
+                               /* motion only */
+                               continue;
+                       }
+               }
+
+               if (code == '\n') {
+                       /* at newline, descent goes down to next baseline,
+                        * which will be down from current baseline
+                        * by height of font */
+                       line_descent += fontheight(font, size);
+                       max_descent = 0.0;
+                       continue;
+               }
+
+               /* music characters inside strings get moved up to the
+                * baseline, so have no descent. */
+               if ( ! (IS_MUSIC_FONT(font)) || (only_mus_sym == YES)) {
+                       d = descent(font, size, code);
+               }
+               else {
+                       d = 0.0;
+               }
+
+               /* if largest descent seen, save this descent */
+               if (d > max_descent) {
+                       max_descent = d;
+               }
+       }
+
+       /* if boxed, allow space for that */
+       if (IS_BOXED(str) == YES) {
+               max_descent += 3.5 * STDPAD;
+       }
+       /* similarly, allow space for circle */
+       if (IS_CIRCLED(str) == YES) {
+               max_descent += circled_dimensions(str, (float *) 0, (float *) 0,
+                                               (float *) 0, (float *) 0);
+       }
+       return(max_descent + line_descent);
+}
+\f
+
+/* return the height of a string in inches. This is the maximum ascent plus the
+ * maximum descent */
+
+double
+strheight(str)
+
+char *str;             /* which string to process */
+{
+       /* Since letters may not
+        * align because of ascent/descent, we get the tallest extent
+        * by adding the largest ascent to the largest descent */
+       return( strascent(str) + strdescent(str));
+}
+\f
+
+/* return the width of a string. This is the sum of the widths of the
+ * individual characters in the string */
+
+double
+strwidth(str)
+char *str;
+{
+       float tot_width;
+       float widest_line;      /* for multi-line strings */
+       float curr_width;
+       double horizontal, vertical;
+       int was_in_pile;        /* if in pile last time through loop */
+       int in_pile_now;        /* if current character is inside a pile */
+       char *s;                /* to walk through string */
+       int font, size, code;
+       int textfont;
+
+
+       if (str == (char *) 0) {
+               return(0.0);
+       }
+
+       /* first 2 bytes are font and size. */
+       font = str[0];
+       size = str[1];
+
+       /* walk through string */
+       was_in_pile = NO;
+       for (curr_width = tot_width = widest_line = 0.0, s = str + 2;
+                       (code = nxt_str_char(&s, &font, &size, &textfont,
+                       &vertical, &horizontal, &in_pile_now, NO)) > 0;
+                       was_in_pile = in_pile_now) {
+
+               /* Piles are handled specially. As soon as we enter a pile,
+                * we call the function to get its entire width. Then for
+                * the rest of the pile, we just skip past everything */
+               if (in_pile_now == YES) {
+                       if (was_in_pile == NO) {
+                               curr_width += pile_width();
+                               if (curr_width > tot_width) {
+                                       tot_width = curr_width;
+                               }
+                       }
+                       continue;
+               }
+
+               /* the horizontal movement coming out of a pile doesn't count,
+                * since it was included in the pile, otherwise it does */
+               if (was_in_pile == NO) {
+                       curr_width += horizontal;
+               }
+               if (curr_width > tot_width) {
+                       tot_width = curr_width;
+               }
+
+               if (code == '\n') {
+                       /* keep track of width line of multi-line string */
+                       if (tot_width > widest_line) {
+                               widest_line = tot_width;
+                       }
+                       tot_width = 0.0;
+                       curr_width = 0.0;
+                       continue;
+               }
+
+               if (code == '\b') {
+                       /* backspace */
+                       tot_width -= backsp_width(size);
+                       curr_width -= backsp_width(size);
+                       continue;
+               }
+
+               /* If we have the special "page number" character,
+                * or special "total number of pages" character,
+                * we deal with that here. */
+               if ( (code == '%' || code == '#') && (s > str + 3)
+                                       && ( (*(s-2) & 0xff) == STR_PAGENUM
+                                       || (*(s-2) & 0xff) == STR_NUMPAGES) ) {
+
+                       char pgnumbuff[8], *pgnum_p;
+
+                       /* convert page number to a string and
+                        * add the width of each character in
+                        * that string. */
+                       (void) sprintf(pgnumbuff, "%d",
+                                       code == '%' ? Pagenum : Last_pagenum);
+
+                       for ( pgnum_p = pgnumbuff; *pgnum_p != '\0';
+                                                               pgnum_p++) {
+                               curr_width += width(font, size, *pgnum_p);
+                       }
+               }
+
+               else {
+                       /* Oh good. This is a normal case. Just add
+                        * width of this character to width so far */
+                       curr_width += width(font, size, code);
+               }
+
+               if (curr_width > tot_width) {
+                       tot_width = curr_width;
+               }
+       }
+       if (tot_width < widest_line) {
+               tot_width = widest_line;
+       }
+       /* if string is boxed, allow space for the box */
+       if (IS_BOXED(str) == YES) {
+               tot_width += 6.0 * STDPAD;
+       }
+       /* similarly, allow space for circled */
+       if (IS_CIRCLED(str) == YES) {
+               (void) circled_dimensions(str, (float *) 0, &tot_width,
+                                               (float *) 0, (float *) 0);
+       }
+       return(tot_width);
+}
+\f
+
+/* Return the width to the "anchor" point of a string. For most strings,
+ * this will be half the width of the first character. But for a string
+ * that begins with things piled atop one another, it is the alignment point.
+ * And for boxed or circled strings, the box or circle must be considered.
+ */
+
+double
+left_width(string)
+
+char *string;
+
+{
+       int font;
+       int size;
+       char *pile_start_p;     /* where pile begins, if any */
+
+       if (starts_piled(string, &font, &size, &pile_start_p) == YES) {
+               return(align_distance(pile_start_p, font, size));
+       }
+       else {
+               int ch;
+               float extra;            /* space for box or circle, if any */
+
+               /* For boxed or circled strings,
+                * the space for the box or circle must be added in */
+               if (IS_BOXED(string) == YES) {
+                       extra = 3.5 * STDPAD;
+               }
+               else if (IS_CIRCLED(string) == YES) {
+                       (void) circled_dimensions(string, (float *) 0,
+                                       (float *) 0, (float *) 0, &extra);
+               }
+               else {
+                       extra = 0.0;
+               }
+
+               /* Get half the width of the first character in the string */
+               font = *string++;
+               size = *string++;
+               ch = next_str_char(&string, &font, &size);
+               return(width(font, size, ch) / 2.0 + extra);
+       }
+}
+\f
+
+/* If string begins with piled text, return YES, otherwise NO,
+ * If YES, also return via pointers the start of the pile and the
+ * font and size at that point. */
+
+static int
+starts_piled(string, font_p, size_p, pile_start_p_p)
+
+char *string;
+int *font_p;
+int *size_p;
+char **pile_start_p_p;
+
+{
+       *font_p = *string++;
+       *size_p = *string++;
+
+       /* walk through string, skipping any leading box/size/font */
+       for (  ; *string != '\0'; string++) {
+               if (IS_STR_COMMAND(*string)) {
+                       switch(*string & 0xff) {
+
+                       case STR_FONT:
+                               *font_p = *(++string);
+                               break;
+
+                       case STR_SIZE:
+                               *size_p = *(++string);
+                               break;
+
+                       case STR_BOX:
+                       case STR_CIR:
+                               break;
+
+                       case STR_PILE:
+                               /* The first thing we found that was not to be
+                                * ignored is the beginning of a pile */
+                               *pile_start_p_p = string;
+                               return(YES);
+               
+                       default:
+                               return(NO);
+                       }
+               }
+               else {
+                       break;
+               }
+       }
+       return(NO);
+}
+\f
+
+/* given a string representing a chord mark, transpose it. For each letter
+ * 'A' to 'G' optionally followed by an accidental, call function to
+ * get transposed value. Build new string with transposed values. Free up
+ * the old string and return the new one. Also, if the accidental was
+ * of the form &, #, x, or && instead of \(smflat) etc, change to proper
+ * music symbol. Also handles translation of o, o/ and ^ to dim, halfdim,
+ * and triangle symbols, and does translation of unescaped accidentals. */
+
+char *
+tranchstr(chordstring, staffno)
+
+char *chordstring;     /* untransposed string */
+int staffno;           /* which staff it is associated with */
+                       /* A staffno of -1 means no transpose, just translate */
+
+{
+       char tmpbuff[128];      /* temporary copy of transposed string */
+       char replacement[4];    /* for dim/halfdim/triangle */
+       short i;                /* index into tmpbuff */
+       unsigned char *str;     /* walk through chordstring */
+       char *transposed;       /* new version of letter[accidental] */
+       char tranbuff[4];       /* to point 'transposed' at if not really
+                                * transposing */
+       char letter;            /* A to G */
+       char accidental;
+       int escaped;            /* YES is accidental was escaped */
+       char literal_accidental;        /* what would normally be translated */
+       int nprocessed;         /* how many character processed by subroutine */
+       char *newstring;        /* final copy of transposed string */
+       int n;
+       int size;
+       int in_pile;            /* YES if inside a pile */
+       int acc_size;           /* size for accidentals */
+
+
+       /* get font/size info */
+       tmpbuff[0] = chordstring[0];
+       tmpbuff[1] = chordstring[1];
+       size = chordstring[1];
+       in_pile = NO;
+       str = (unsigned char *) (chordstring + 2);
+       literal_accidental = '\0';  /* avoids bogus "used before set" warning */
+
+       /* walk through original string */
+       for (i = 2; *str != '\0'; str++) {
+
+               /* Be safe. Bail out a little before we reach end,
+                * because some things take several bytes,
+                * and it's easiest to just check once per loop. */
+               if (i > sizeof(tmpbuff) - 8) {
+                       ufatal("chord string too long: '%s'", chordstring + 2);
+               }
+
+               acc_size = accsize(size);
+
+               /* If a STR_*, deal with that */
+               if ((n = str_cmd((char *) str, &size, &in_pile)) > 0) {
+                       strncpy(tmpbuff + i, (char *) str, (unsigned) n);
+                       i += n;
+                       str += n - 1;
+               }
+
+               /* handle backslashed o and ^ */
+               else if (*str == '\\' && ( *(str+1) == 'o' || *(str+1) == '^' ) ) {
+                       str++;
+                       tmpbuff[i++] = *str;
+               }
+
+               else if (*str >= 'A' && *str <= 'G') {
+
+                       /* Aha! Something to transpose. */
+                       letter = *str;
+
+                       str += get_accidental( (unsigned char *) (str + 1),
+                                       &accidental, &acc_size, NO, &escaped);
+                       if (escaped == YES) {
+                               /* not *really* an accidental, so save to
+                                * print later. */
+                               literal_accidental = accidental;
+                               accidental = '\0';
+                       }
+                       if (staffno == -1) {
+                               /* not to be transposed, so make a string
+                                * that would be like what tranchnote() would
+                                * return, but with no transposition. */
+                               tranbuff[0] = letter;
+                               tranbuff[1] = accidental;
+                               tranbuff[2] = '\0';
+                               transposed = tranbuff;
+                       }
+                       else {
+                               /* get the transposed value */
+                               transposed = tranchnote(letter, accidental, staffno);
+                       }
+
+                       /* put transposed letter into output */
+                       tmpbuff[i++] = *transposed;
+
+                       /* now add accidental if any */
+                       i += add_accidental(tmpbuff + i, (int) *++transposed,
+                                                       acc_size, NO);
+
+                       /* add on any escaped pseudo-accidental */
+                       if (escaped == YES) {
+                               i += add_accidental(tmpbuff + i,
+                                       (int) literal_accidental,
+                                       acc_size, YES);
+                               escaped = NO;
+                       }
+
+                       /* handle dim/halfdim/triangle transformations */
+                       if ((n = dim_tri(str + 1, replacement, size, YES)) > 0) {
+                               strcpy(tmpbuff + i, replacement);
+                               i += strlen(replacement);
+                               str += n;
+                       }
+               }
+               else {
+                       /* Originally we only translated things like # and &
+                        * in chords to musical accidental symbols if they
+                        * immediately followed a letter A-G. But due to
+                        * popular demand, they are now translated everywhere,
+                        * unless escaped. */
+                       nprocessed = get_accidental( (unsigned char *) str,
+                                       &accidental, &acc_size, NO, &escaped);
+                       if (nprocessed > 0) {
+                               i += add_accidental(tmpbuff + i,
+                                       (int) accidental,
+                                       acc_size, escaped);
+                               /* the -1 is because str will get incremented
+                               * at the top of the 'for' */
+                               str += nprocessed - 1;
+                       }
+                       else {
+                               /* something boring. Just copy */
+                               tmpbuff[i++] = *str;
+                       }
+               }
+       }
+
+       /* need to make permanent copy of new string */
+       tmpbuff[i++] = '\0';
+       MALLOCA(char, newstring, i + 1);
+       (void) memcpy(newstring, tmpbuff, (unsigned) i);
+
+       /* free original version */
+       FREE(chordstring);
+
+       /* return new, transposed version */
+       return(newstring);
+}
+\f
+
+/* If there is a STR_* command in chord/analysis/figbass, return how
+ * many characters long it is. Also update the size if the
+ * command was one to change size, and update pile status if necessary. */
+
+static int
+str_cmd(str, size_p, in_pile_p)
+
+char *str;     /* check string starting here */
+int *size_p;
+int *in_pile_p;        /* YES if in pile, may be updated */
+
+{
+       if (IS_STR_COMMAND(*str)) {
+               switch(*str & 0xff) {
+
+               case STR_SIZE:
+                       /* update size */
+                       *size_p = *(str + 1);
+                       /* command plus 1 argument byte */
+                       return(2);
+
+               case STR_PAGENUM:
+               case STR_NUMPAGES:
+               case STR_FONT:
+               case STR_BACKSPACE:
+               case STR_VERTICAL:
+                       /* command plus 1 argument byte */
+                       return(2);
+
+               case STR_MUS_CHAR:
+                       /* command plus 2 argument bytes */
+                       return(3);
+
+               case STR_PILE:
+                       /* entering/leaving a pile alters the size */
+                       *size_p = pile_size(*size_p, *in_pile_p);
+                       *in_pile_p = (*in_pile_p ? NO : YES);
+                       break;
+
+               default:
+                       /* others have no argument bytes */
+                       return(1);
+               }
+       }
+       return(0);
+}
+\f
+
+/* Check the first character of the given string to see if it is an accidental
+ * or something that should be translated to an accidental (# & x && and
+ * maybe n). If so, fill in the accidental. If the accidental was specified
+ * via a STR_MUS_CHAR, also update the accidental size.
+ * If no accidental, accidental_p will will filled in
+ * with '\0'. In any case return how many bytes were processed.
+ */
+
+static int
+get_accidental(string, accidental_p, acc_size_p, trans_natural, escaped_p)
+
+unsigned char *string; /* check this for an accidental */
+char *accidental_p;    /* return the accidental here, or \0 if none */
+int *acc_size_p;       /* return the accidental size here */
+int trans_natural;     /* if YES, translate n to natural, else leave as n */
+int *escaped_p;                /* Return value: YES if the symbol was backslashed */
+
+{
+       unsigned char *str_p;
+
+       str_p = string;
+       /* assume no accidental */
+       *accidental_p = '\0';
+
+       /* check if escaped */
+       if (*str_p == '\\') {
+               *escaped_p = YES;
+               str_p++;
+       }
+       else {
+               *escaped_p = NO;
+       }
+
+       /* See if the following character is an accidental */
+       switch (*str_p) {
+
+       case '#':
+       case 'x':
+               *accidental_p = *str_p++;
+               break;
+       case '&':
+               *accidental_p = *str_p++;
+               /* have to peek ahead to check for double flat,
+                * but not if escaped, so person can get a literal
+                * ampersand followed by a flat. */
+               if (*escaped_p == NO && *str_p == '&') {
+                       /* double flat is 'B' internally */
+                       *accidental_p = 'B';
+                       str_p++;
+               }
+               break;
+
+       case 'n':
+               /* naturals are not translated in chords, but are
+                * in analysis and figbass */
+               if (trans_natural == YES) {
+                       *accidental_p = *str_p++;
+               }
+               break;
+
+       case STR_MUS_CHAR:
+               if (*escaped_p == YES) {
+                       break;
+               }
+               /* Check if user put in \(flat) or something
+                * similar. If so, use that. */
+               switch (*(str_p + 2)) {
+               case C_FLAT:
+                       *acc_size_p = *(str_p + 1);
+                       *accidental_p = '&';
+                       str_p += 3;
+                       break;
+
+               case C_SHARP:
+                       *acc_size_p = *(str_p + 1);
+                       *accidental_p = '#';
+                       str_p += 3;
+                       break;
+
+               case C_DBLFLAT:
+                       *acc_size_p = *(str_p + 1);
+                       *accidental_p = 'B';
+                       str_p += 3;
+                       break;
+
+               case C_DBLSHARP:
+                       *acc_size_p = *(str_p + 1);
+                       *accidental_p = 'x';
+                       str_p += 3;
+                       break;
+
+               case C_NAT:
+                       /* Always translate the natural symbol,
+                        * even when trans_natural is NO. That really
+                        * applies just to the use of 'n' which is
+                        * likely to be wanted as a real n, whereas
+                        * a music symbol natural is unambiguous. */
+                       *acc_size_p = *(str_p + 1);
+                       *accidental_p = 'n';
+                       str_p += 3;
+                       break;
+
+               default:
+                       /* false alarm. Some other
+                        * music character. */
+                       break;
+               }
+               break;
+
+       default:
+               /* nothing special */
+               break;
+       }
+
+       /* If all we saw was a backslash,
+        * then there wasn't really an accidental */
+       if (*escaped_p == YES && str_p == string + 1) {
+               *escaped_p = NO;
+               str_p = string;
+       }
+
+       return(str_p - string);
+}
+\f
+
+/* Write the given accidental in the given size to the given string.
+ * Return how many bytes were added. */
+
+static int
+add_accidental(buff, acc_character, acc_size, escaped)
+
+char *buff;            /* write into this buffer */
+int acc_character;     /* write this accidental */
+int acc_size;          /* make accidental this big */
+int escaped;           /* if YES, was escaped, so not really an accidental;
+                        * print it as a normal character */
+
+{
+       if (acc_character != '\0') {
+
+               /* if escaped, just treat like normal character. */
+               if (escaped == YES) {
+                       buff[0] = acc_character;
+                       return(1);
+               }
+
+               /* sharps and naturals are tall enough that they can
+                * make things not line up, so move them down some */
+               if (acc_character == '#' || acc_character == 'n') {
+                       buff[0] = (char) STR_VERTICAL;
+                       buff[1] = (char) ENCODE_VERT(-4);
+                       buff += 2;
+               }
+               /* has accidental. Add STR_MUS_CHAR-size-code */
+               buff[0] = (char) STR_MUS_CHAR;
+
+               /* double sharp is special. It is too small,
+                * so make it bigger */
+               if (acc_character == 'x') {
+                       acc_size = (int) ( (float) acc_size
+                                                       * 1.25);
+               }
+               buff[1] = (char) acc_size;
+
+               /* use accidental of appropriate type */
+               switch (acc_character) {
+
+               case '#':
+                       buff[2] = C_SHARP;
+                       break;
+
+               case '&':
+                       buff[2] = C_FLAT;
+                       break;
+
+               case 'x':
+                       buff[2] = C_DBLSHARP;
+                       break;
+
+               case 'B':
+                       buff[2] = C_DBLFLAT;
+                       break;
+
+               case 'n':
+                       buff[2] = C_NAT;
+                       break;
+
+               default:
+                       pfatal("illegal accidental on transposed chord");
+                       break;
+               }
+               if (acc_character == '#' || acc_character == 'n') {
+                       buff[3] = (char) STR_VERTICAL;
+                       buff[4] = (char) ENCODE_VERT(4);
+                       /* We added 3 bytes for the accidental, plus
+                        * 2 bytes before and after for vertical motion. */
+                       return(7);
+               }
+               else {
+                       return(3);      /* we added 3 bytes */
+               }
+       }
+
+       return (0);
+}
+\f
+
+/* In chords and such, "o" becomes \(dim), "o/" becomes \(halfdim)
+ * unless followed by [A-G] in which case it becomes "\(dim)/",
+ * and "^" becomes \(triangle). Return number of characters processed.
+ */
+
+static int
+dim_tri(str_p, replacement, size, is_chord)
+
+unsigned char *str_p;          /* check string at this point */
+char *replacement;             /* return the replacement in this buffer,
+                                * which needs to be at least 4 bytes long */
+int size;                      /* use this size for music character */
+int is_chord;                  /* YES for chord, NO for analysis/figbass */
+
+{
+       if (*str_p == '^') {
+               replacement[0] = (char) STR_MUS_CHAR;
+               replacement[1] = size;
+               replacement[2] = C_TRIANGLE;
+               replacement[3] = '\0';
+               return(1);
+       }
+       else if (*str_p == 'o') {
+               replacement[0] = (char) STR_MUS_CHAR;
+               replacement[1] = size;
+               replacement[3] = '\0';
+               if ( *(str_p+1) == '/' && (is_chord == NO ||
+                               (*(str_p+2) < 'A' || *(str_p+2) > 'G'))) {
+                       replacement[2] = C_HALFDIM;
+                       return(2);
+               }
+               else {
+                       replacement[2] = C_DIM;
+                       return(1);
+               }
+       }
+       return(0);
+}
+\f
+
+/* Given a string for analysis or figbass, transform the accidentals
+ * & # && x n to their music characters.
+ */
+
+char *
+acc_trans(string)
+
+char *string;
+
+{
+       char buffer[128];       /* output buffer for transformed string */
+       char *out_p;            /* current location in output buffer */
+       char replacement[4];    /* space for dim, halfdim, etc */
+       int n;
+       int size, acc_size;
+       char accidental;        /* #, &, x, etc */
+       int escaped;            /* YES is accidental was escaped */
+       int in_pile;            /* YES if inside a pile */
+
+
+       buffer[0] = string[0];
+       buffer[1] = string[1];
+       size = string[1];
+       in_pile = NO;
+
+       /* walk through string, transforming any accidentals along the way */
+       for ( string += 2, out_p = buffer + 2; *string != '\0'; ) {
+               /* Be safe. Bail out a little before we reach end,
+                * because some things take several bytes,
+                * and it's easiest to just check once per loop. */
+               if (out_p - buffer > sizeof(buffer) - 8) {
+                       l_ufatal(Curr_filename, yylineno,
+                               "analysis or figbass string too long");
+               }
+
+               acc_size = accsize(size);
+               if ((n = get_accidental((unsigned char *) string,
+                               &accidental, &acc_size, YES, &escaped)) > 0 ) {
+                       out_p += add_accidental(out_p, (int) accidental,
+                                               acc_size, escaped);
+                       string += n;
+               }
+               else if (*string == '\\' && ( *(string+1) == 'o' || *(string+1) == '^') ) {
+                       *out_p++ = *++string;
+                       string++;
+               }
+               else if ((n = dim_tri((unsigned char *) string, replacement,
+                                                       size, NO)) > 0) {
+                       strcpy(out_p, replacement);
+                       out_p += strlen(replacement);
+                       string += n;
+               }
+               else if ((n = str_cmd(string, &size, &in_pile)) > 0) {
+                       strncpy(out_p, string, (unsigned) n);
+                       out_p += n;
+                       string += n;
+               }
+               else {
+                       *out_p++ = *string++;
+               }
+       }
+       *out_p = '\0';
+
+       return(copy_string(buffer + 2, buffer[0], buffer[1]));
+}
+\f
+/* Given a chord, analysis or figbass string,
+ * transform according to their special rules:
+ *     - : gets translated to \: and vice-versa
+ *     - figbass starts in piled mode
+ *     - in figbass, a / gets translated to \/ and vice-versa
+ * This string will be in half transformed state: the first 2 bytes
+ * are font/size, but the rest is still all ASCII, not internal format.
+ */
+
+char *
+modify_chstr(string, modifier)
+
+char *string;
+int modifier;
+
+{
+       int length;     /* of modified string */
+       char *s;        /* walk through string */
+       char *newstring;
+       char *new_p;    /* walk through newstring */
+       int need_new;   /* if we need to make a new string */
+
+
+       length = strlen(string);
+       if (modifier == TM_FIGBASS) {
+               /* We'll need two extra bytes for
+                * the leading \: for pile mode. */
+               length += 2;
+               need_new = YES;
+       }
+       else {
+               /* Only need a new string if the original has colons,
+                * so assume for now we won't need a new string */
+               need_new = NO;
+       }
+
+       /* Figure out how much space we'll need for the modified string.
+        * Any unbackslashed colons will take up an extra byte once
+        * we backslash it. But any backslashed one will take up one
+        * less when we unescape it. Similar for slashes in figbass. */
+       for (s = string + 2; *s != '\0'; s++) {
+               if (*s == ':') {
+                       length++;
+                       need_new = YES;
+               }
+               else if (modifier == TM_FIGBASS && *s == '/') {
+                       /* o/ means half diminished so that doesn't count */
+                       if (s > string + 2 && *(s-1) == 'o') {
+                               continue;
+                       }
+                       length++;
+                       need_new = YES;
+               }
+               else if (*s == '\\') {
+                       s++;
+                       /* things that occur inside \(...) don't count */
+                       if (*s == '(') {
+                               for (s++; *s != '\0' && *s != ')'; s++) {
+                                       ;
+                               }
+                               /* If no closing parenthesis, return as is;
+                                * later code will catch that */
+                               if (*s == '\0') {
+                                       return(string);
+                               }
+                       }
+                       else if (*s == ':') {
+                               length--;
+                               need_new = YES;
+                       }
+                       else if (modifier == TM_FIGBASS && *s == '/') {
+                               length--;
+                               need_new = YES;
+                       }
+               }
+       }
+
+       /* If string is okay as is, we are done here */
+       if (need_new == NO) {
+               return(string);
+       }
+
+       /* get enough space for new string */
+       MALLOCA(char, newstring, length + 1);
+
+       /* copy font/size */
+       newstring[0] = string[0];
+       newstring[1] = string[1];
+
+       new_p = newstring + 2;
+       s = string + 2;
+       if (modifier == TM_FIGBASS) {
+               /* add \: but after box, if any */
+               if (string[2] == '\\' && string[3] == '[') {
+                       *new_p++ = *s++;
+                       *new_p++ = *s++;
+               }
+               *new_p++ = '\\';
+               *new_p++ = ':';
+       }
+
+       /* walk through rest of string, copying, but transforming
+        * any slashes and colons along the way */
+       for (  ; *s != '\0'; s++, new_p++) {
+
+               /* handle colons */
+               if (*s == ':') {
+                       /* add a backslash */
+                       *new_p++ = '\\';
+               }
+               else if (*s == '\\' && *(s+1) == ':') {
+                       /* skip past the backslash */
+                       s++;
+               }
+
+               /* handle slashes in figbass */
+               else if (modifier == TM_FIGBASS) {
+                       if (*s == '/') {
+                               /* o/ means half diminished
+                                * so that doesn't count */
+                               if (s <= string + 2 || *(s-1) != 'o') {
+                                       /* add a backslash */
+                                       *new_p++ = '\\';
+                               }
+                       }
+                       else if (*s == '\\' && *(s+1) == '/') {
+                               /* skip past the backslash */
+                               s++;
+                       }
+               }
+
+               /* copy from original string to new one */
+               *new_p = *s;
+       }
+
+       /* original is now no longer needed */
+       FREE(string);
+
+       /* terminate and return the modified string */
+       *new_p = '\0';
+       return(newstring);
+}
+\f
+
+/* given an integer point size, return the integer point size appropriate
+ * for a "small" version. This is SM_FACTOR times the size, rounded, but
+ * not less than 1. */
+
+static int
+smallsize(size)
+
+int size;
+
+{
+       size = (int) ( (float) size * SM_FACTOR);
+       if (size < 1) {
+               size = 1;
+       }
+       return(size);
+}
+\f
+
+/* accidentals in chords need to be scaled. Given a size, return the size
+ * that an accidental should be. This is 60% of given size, rounded to
+ * an integer, but no smaller than 1. */
+
+static int
+accsize(size)
+
+int size;
+
+{
+       size = (int) ( (float) size * 0.6);
+       if (size < 1) {
+               size = 1;
+       }
+       return(size);
+}
+\f
+
+/* return which character to use for rest, based on basictime */
+
+int
+restchar(basictime)
+
+int basictime;
+
+{
+       if (basictime < -1) {
+               pfatal("tried to get rest character for multirest");
+               /*NOTREACHED*/
+               return(0);
+       }
+
+       else if (basictime == -1) {
+               /* quad rest */
+               return (C_QWHREST);
+       }
+
+       else if (basictime == 0) {
+               /* double whole rest */
+               return (C_DWHREST);
+       }
+
+       else {
+               /* other non-multirest */
+               return (Resttab [ drmo(basictime) ] );
+       }
+}
+\f
+
+/* return YES if given font is an italic font (includes boldital too) */
+
+int
+is_ital_font(font)
+
+int font;
+
+{
+       return(Fontinfo[ font_index(font) ].is_ital);
+}
+\f
+
+/* given a string, return, via pointers the font and size in effect at the
+ * end of the string */
+
+void
+end_fontsize(str, font_p, size_p)
+
+char *str;             /* check this string */
+int *font_p;           /* return font at end of str via this pointer */
+int *size_p;           /* return size at end of str via this pointer */
+
+{
+       if (str == (char *) 0) {
+               /* empty string, use defaults */
+               *font_p = FONT_TR;
+               *size_p = DFLT_SIZE;
+               return;
+       }
+
+       /* find the font/size in effect at end of given string */
+       *font_p = *str++;
+       *size_p = *str++;
+       while (next_str_char(&str, font_p, size_p) != '\0') {
+               ;
+       }
+}
+\f
+
+/* given a string, return a string made up of a dash in the font and size
+ * of the end of the given string. However, if the string ends with a ~ or _
+ * return a string containing that instead */
+
+char *
+dashstr(str)
+
+char *str;     /* return dash with same font/size as end of this string */
+
+{
+       int font, size;
+       char *newstring;
+       int ch;         /* character to use */
+
+
+       end_fontsize(str, &font, &size);
+       ch = last_char(str);
+       if (ch != '~' && ch != '_') {
+               ch = '-';
+       }
+
+       /* allocate space for dash string and fill it in */
+       MALLOCA(char, newstring, 4);
+       newstring[0] = (char) font;
+       newstring[1] = (char) size;
+       newstring[2] = (char) ch;
+       newstring[3] = '\0';
+       return(newstring);
+}
+\f
+
+/* Given an internal format string, create an ASCII-only string. Flags
+ * tell how complete a conversion to do. If verbose is YES, try to convert
+ * everything back to user's original input, otherwise ignore special things
+ * other than music characters, extended characters, and backspace.
+ * If pagenum is YES, interpolate the current page number rather than using %.
+ *
+ * Recreating the original user string is not perfect, but is usually right.
+ * Where there are shortcuts, we can't tell if user used them or not.
+ * Extended characters are output by name even if user put them in as single
+ * Latin-1 characters. But we couldn't use the Latin-1 hyper-ASCII in midi
+ * anyway, because they have high bit set.
+ *
+ * Returns the ASCII-ized string, which is stored in an area that will get
+ * overwritten on subsequent calls, so if caller needs a permanent copy,
+ * they have to make it themselves.
+ */
+
+/* This is how much to malloc at a time to hold the ASCII-ized string */
+#define ASCII_BSIZE    512
+
+char *
+ascii_str(string, verbose, pagenum, textmod)
+
+char *string;  /* internal format string to convert */
+int verbose;   /* If YES, try to reproduce user's original input */
+int pagenum;   /* YES (interpolate number for \%) or NO (leave \% as is) */
+int textmod;   /* TM_ value */
+
+{
+       static char *buff = 0;          /* for ASCII-ized string */
+       static unsigned buff_length = 0;/* how much is malloc-ed */
+       int i;                          /* index into ASCII-ized string */
+       char *musname;                  /* music character name */
+       int in_pile = NO;
+       char *str;                      /* walk through string */
+       int musfont;                    /* FONT_MUSIC*    */
+
+
+       /* first time, get some space */
+       if (buff_length == 0) {
+               buff_length = ASCII_BSIZE;
+               MALLOCA(char, buff, buff_length);
+       }
+
+       /* walk through string */
+       i = 0;
+       /* special case: normally we implicitly begin a figbass with a
+        * pile start, but if users cancels that, it won't be there */
+       if (textmod == TM_FIGBASS &&
+                       (((unsigned char) *(string+2)) & 0xff) != STR_PILE) {
+               buff[i++] = ':';
+       }
+       for (str = string + 2;  *str != '\0'; str++) {
+               switch ( ((unsigned char) *str) & 0xff) {
+
+               case STR_FONT:
+                       str++;
+#ifdef EXTCHAR
+                       if ( (int) *str > EXT_FONT_OFFSET) {
+                               str++;
+                               /* translate to Mup name */
+                               (void) sprintf(buff + i, "\\(%s)",
+                                               ext_num2name((int) *str));
+                               while (buff[i] != '\0') {
+                                       i++;
+                               }
+                               /* skip past the return to original font */
+                               str += 2;
+                       }
+                       else if (verbose == YES) {
+#else
+                       if (verbose == YES) {
+#endif
+                               (void) sprintf(buff + i, "\\f(%s)",
+                                               fontnum2name((int) *str));
+                               while (buff[i] != '\0') {
+                                       i++;
+                               }
+                       }
+                       break;
+
+               case STR_SIZE:
+                       str++;
+                       if (verbose == YES) {
+                               (void) sprintf(buff + i, "\\s(%d)", (int) *str);
+                               while (buff[i] != '\0') {
+                                       i++;
+                               }
+                       }
+                       break;
+
+               case STR_VERTICAL:
+                       str++;
+                       if (verbose == YES) {
+                               (void) sprintf(buff + i, "\\v(%d)",
+                                               DECODE_VERT((int) *str) * 100
+                                               / MAXVERTICAL);
+                               while (buff[i] != '\0') {
+                                       i++;
+                               }
+                       }
+                       break;
+               
+               case STR_MUS_CHAR:
+               case STR_MUS_CHAR2:
+                       musfont = str2mfont( ((unsigned char) *str) & 0xff);
+
+                       /* skip past the size byte,
+                        * and on to the character code. */
+                       str += 2;
+                       /* In chordlike stuffs, we translate things like
+                        * # and &&, so translate them back. It's possible
+                        * the user used the names explicitly rather than us
+                        * translating, in which case this won't be
+                        * strictly what they put in, but it will be
+                        * consistent, so that a caller of this function
+                        * can easily sort or compare values
+                        * without having to know (for example)
+                        * that '#' and \(smsharp) are the same thing.  */
+                       musname = 0;
+                       if (IS_CHORDLIKE(textmod) == YES
+                                               && musfont == FONT_MUSIC) {
+                               switch( ((unsigned char) *str) & 0xff) {
+                               case C_SHARP:
+                                       musname = "#";
+                                       break;
+                               case C_FLAT:
+                                       musname = "&";
+                                       break;
+                               case C_DBLSHARP:
+                                       musname = "x";
+                                       break;
+                               case C_DBLFLAT:
+                                       musname = "&&";
+                                       break;
+                               case C_NAT:
+                                       if (textmod != TM_CHORD) {
+                                               musname = "n";
+                                       }
+                                       break;
+                               case C_DIM:
+                                       musname = "o";
+                                       break;
+                               case C_HALFDIM:
+                                       musname = "o/";
+                                       break;
+                               case C_TRIANGLE:
+                                       musname = "^";
+                                       break;
+                               default:
+                                       break;
+                               }
+                       }
+                       if (musname != 0) {
+                               (void) sprintf(buff + i, musname);
+                       }
+                       else {
+                               (void) sprintf(buff + i, "\\(%s)", 
+                                       mc_num2name((int) *str, musfont));
+                       }
+                       while (buff[i] != '\0') {
+                               i++;
+                       }
+                       
+                       break;
+
+               case STR_BACKSPACE:
+                       if (verbose == YES) {
+                               buff[i++] = '\\';
+                               buff[i++] = 'b';
+                       }
+                       /* ignore this and following char */
+                       str++;
+                       break;
+
+               case STR_PRE:
+               case STR_PST:
+                       if (verbose == YES) {
+                               buff[i++] = '<';
+                       }
+                       break;
+
+               case STR_U_PRE:
+               case STR_U_PST:
+                       if (verbose == YES) {
+                               buff[i++] = '<';
+                               buff[i++] = '^';
+                       }
+                       break;
+
+               case STR_PRE_END:
+               case STR_PST_END:
+                       if (verbose == YES) {
+                               buff[i++] = '>';
+                       }
+                       break;
+
+               case STR_BOX:
+                       if (verbose == YES) {
+                               buff[i++] = '\\';
+                               buff[i++] = '[';
+                       }
+                       break;
+
+               case STR_BOX_END:
+                       if (verbose == YES) {
+                               buff[i++] = '\\';
+                               buff[i++] = ']';
+                       }
+                       break;
+
+               case STR_CIR:
+                       if (verbose == YES) {
+                               buff[i++] = '\\';
+                               buff[i++] = '{';
+                       }
+                       break;
+
+               case STR_CIR_END:
+                       if (verbose == YES) {
+                               buff[i++] = '\\';
+                               buff[i++] = '}';
+                       }
+                       break;
+
+               case STR_C_ALIGN:
+                       if (verbose == YES) {
+                               buff[i++] = '\\';
+                               buff[i++] = '^';
+                       }
+                       break;
+
+               case STR_L_ALIGN:
+                       if (verbose == YES) {
+                               buff[i++] = '\\';
+                               buff[i++] = '|';
+                       }
+                       break;
+
+               case STR_PILE:
+                       if (verbose == YES) {
+                               /* On figbass, we implictly add a pile start */
+                               if (textmod == TM_FIGBASS && string + 2 == str) {
+                                       ;
+                               }
+                               /* if this is at the end of a padded string,
+                                * there is a high probability it is one
+                                * we added implicitly, so skip it */
+                               else if (in_pile == YES && *(str+1) == ' ' &&
+                                               *(str+2) == '\0') {
+                                       ;
+                               }
+                               else {
+                                       /* in chordlike things, user didn't
+                                        * use a backslash, else they did */
+                                       if (IS_CHORDLIKE(textmod) == NO) {
+                                               buff[i++] = '\\';
+                                       }
+                                       buff[i++] = ':';
+                               }
+                       }
+                       /* keep track of toggle state */
+                       in_pile = (in_pile == YES ? NO : YES);
+                       break;
+
+               case STR_SLASH:
+                       if (verbose == YES && textmod != TM_FIGBASS) {
+                               buff[i++] = '\\';
+                       }
+                       buff[i++] = '/';
+                       break;
+
+               case STR_PAGENUM:
+               case STR_NUMPAGES:
+                       if (pagenum == YES) {
+                               /* Write page number and update length.
+                                * Actually, we don't have the correct values
+                                * for this until late in program execution,
+                                * and for MIDI, there are no pages at all,
+                                * and this can be called from MIDI, so
+                                * this is probably not really very useful,
+                                * but this is the best we can do... */
+                               (void) sprintf(buff + i, "%d",
+                                       (((unsigned char) *str) & 0xff)
+                                       == STR_PAGENUM ?
+                                       Pagenum : Last_pagenum);
+                               while (buff[i] != '\0') {
+                                       i++;
+                               }
+                       }
+                       else {
+                               buff[i++] = '\\';
+                               buff[i++] = *(str+1);
+                       }
+                       str++;
+                       break;
+
+               case '\\':
+                       buff[i++] = '\\';
+                       buff[i++] = '\\';
+                       break;
+
+               default:
+                       if (*str == '\n') {
+                               if (in_pile == YES) {
+                                       if ( *(str+1) != '\0') {
+                                               buff[i++] = ' ';
+                                       }
+                               }
+                               else {
+                                       buff[i++] = '\\';
+                                       buff[i++] = 'n';
+                               }
+                       }
+                       else if (IS_CHORDLIKE(textmod) == YES && *str == ':') {
+                               buff[i++] = '\\';
+                               buff[i++] = ':';
+                       }
+                       else if (textmod == TM_FIGBASS && *str == '/') {
+                               buff[i++] = '\\';
+                               buff[i++] = '/';
+                       }
+                       else if (*str == ' ' && *(str+1) == '\0') {
+                               /* This is probably a space padding
+                                * that we added implicitly,
+                                * so don't print it. If this is
+                                * called on a 'with' item or 'print' item
+                                * where user explicitly added a space,
+                                * this will strip that off, which, strictly
+                                * speaking, it shouldn't. But that would
+                                * only be for debugging anyway, and a
+                                * strange case, so don't worry about it. */
+                               ;
+                       }
+                       else {
+                               /* ordinary character */
+                               buff[i++] = *str;
+                       }
+               }
+
+               /* If running low on space, get some more. Could probably
+                * just truncate, since this is used for things like error
+                * messages, but alloc-ing more is easy enough. */
+               if (i > buff_length - 20) {
+                       buff_length += ASCII_BSIZE;
+                       REALLOCA(char, buff, buff_length);
+               }
+       }
+       buff[i++] = '\0';
+
+       return(buff);
+}
+\f
+
+/*
+ * Given a text string and a maximum desired width, try adding newlines at
+ * white space to bring the width down under the desired width. If that's
+ * not possible, do the best we can. Return pointer to the possibly
+ * altered string.
+ */
+
+char *
+split_string(string, desired_width)
+
+char *string;
+double desired_width;
+
+{
+       char *last_white_p;     /* where last white space was */
+       char *curr_white_p;     /* white space we're dealing with now */
+       char *str;              /* to walk through string */
+       double proposed_width;  /* width of string so far */
+       int font, size;
+       int c;                  /* the current character in string */
+       int save_c;             /* temporary copy of c */
+       int save_str;           /* temporary copy of character from string */
+
+
+       /* Piles are incompatible with newlines, so we don't want to
+        * even attempt to split a string with a pile in it. */
+       for (str = string + 2; *str != '\0'; str++) {
+               if ((*str & 0xff) == STR_PILE) {
+                       /* string has a pile, so return it as is */
+                       return(string);
+               }
+       }
+
+       /* Go through the string, until we hit white space. */
+       last_white_p = (char *) 0;
+       font = string[0];
+       size = string[1];
+       str = string + 2;
+       while ((c = next_str_char(&str, &font, &size)) != '\0') {
+
+               /* Are we at white space? */
+               if ( ! IS_MUSIC_FONT(font) && (c == ' ' || c == '\t')) {
+
+                       /* Temporarily replace with newline, and terminate
+                        * to get width so far if we were to add a newline */
+                       curr_white_p = str - 1;
+                       save_c = c;
+                       save_str = *str;
+                       *curr_white_p = '\n';
+                       *str = '\0';
+                       proposed_width = strwidth(string);
+                       *curr_white_p = save_c;
+                       *str = save_str;
+
+                       if (proposed_width > desired_width) {
+                               if (last_white_p != (char *) 0) {
+                                       /* reduce the width of the string by
+                                        * changing the most recent white space
+                                        * to a newline */
+                                       *last_white_p = '\n';
+
+                                       /* if the overall string is now short
+                                        * enough, we are done */
+                                       if (strwidth(string) <= desired_width) {
+                                               return(string);
+                                       }
+                                       last_white_p = curr_white_p;
+                               }
+                               else {
+                                       /* No previous white space, so we
+                                        * can't make it short enough. So change
+                                        * this current white space to a
+                                        * newline, since that's the best we
+                                        * can do. But also set the desired
+                                        * width to our current width,
+                                        * because we know we're
+                                        * going to have to be at least this
+                                        * wide anyway, so we might as well use
+                                        * this much space on future lines */
+                                       *curr_white_p = '\n';
+                                       desired_width = proposed_width;
+
+                                       /* no longer have a previous
+                                        * white space on the current line,
+                                        * because we just started a new
+                                        * line */
+                                       last_white_p = (char *) 0;
+                               }
+
+                       }
+                       else {
+                               /* not too wide yet. Remember where this white
+                                * space is, in case the next word makes us
+                                * too wide and we have to change it to a
+                                * newline */
+                               last_white_p = curr_white_p;
+                       }
+               }
+       }
+
+       /* If last word went over the edge, move to next line if possible. */
+       if (strwidth(string) > desired_width && last_white_p != (char *) 0) {
+               *last_white_p = '\n';
+       }
+
+       /* Return the (possibly altered) string */
+       return(string);
+}
+\f
+
+/* Given a point size and an adjustment factor, return a new point size.
+ * If size would be less than MINSIZE, return MINSIZE.
+ * If it would be greater than MAXSIZE, print error and return MAXSIZE.
+ * Since we only use integer sizes, there may be some roundoff error.
+ * While it would be possible to dream up a pathological case
+ * where this roundout might be big enough to notice,
+ * for any sane scenario you would probably need
+ * an extremely high resolution printer and a microscope to notice.
+ */
+
+int
+adj_size(size, scale_factor, filename, lineno)
+
+int size;              /* original point size */
+double scale_factor;   /* multiply original size by this factor */
+char *filename;                /* filename and lineno are for error messages */
+int lineno;
+
+{
+       size = (int) ((double) size * scale_factor + 0.5);
+       if (size < MINSIZE) {
+               return(MINSIZE);
+       }
+       if (size > MAXSIZE) {
+               l_warning(filename, lineno,
+                       "Adjusted size of string would be bigger than %d", MAXSIZE);
+               return(MAXSIZE);
+       }
+       return(size);
+}
+\f
+
+/* Given a string that is in internal format, and a scale factor by which to
+ * resize that string, adjust all size bytes in the string.
+ */
+
+char *
+resize_string(string, scale_factor, filename, lineno)
+
+char *string;          /* this is the string to adjust */
+double scale_factor;   /* adjust sizes in string by this factor */
+char *filename;                /* for error messages */
+int lineno;            /* for error messages */
+
+{
+       char *s;        /* to walk through string */
+
+
+       /* if string is empty, nothing to do */
+       if (string == (char *) 0 || *string == '\0') {
+               return(string);
+       }
+
+       /* if factor is sufficiently close to 1.0 that it's very clear
+        * we won't be making any changes (since we only use integer
+        * point sizes), don't bother */
+       if ( fabs( (double) (scale_factor - 1.0)) < 0.01) {
+               return(string);
+       }
+
+       /* second byte is size byte, so adjust that */
+       string[1] = (char) adj_size( (int) string[1], scale_factor,
+                                                       filename, lineno);
+
+       /* Go through the string. For each size byte, replace it with an
+        * adjusted size. Size bytes occur immediately after STR_SIZE
+        * and STR_MUS_CHAR commands. Everything else can get copied as
+        * is: STR_BACKSPACE is in terms of the default size, so it is
+        * unaffected by this resizing, and the other special string commands
+        * are unrelated to size and thus unaffected. */
+       for (s = string + 2; *s != '\0'; s++) {
+               switch ( (unsigned char) *s ) {
+               case STR_SIZE:
+               case STR_MUS_CHAR:
+                       s++;
+                       *s = (char) adj_size( (int) *s, scale_factor,
+                                                       filename, lineno);
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       return(string);
+}
+\f
+
+/* Given a circled string, return how much to add to its ascent and
+ * descent to give room for the circle.  If pointer arguments are non-zero,
+ * return additional values via those pointers.
+ */
+
+double
+circled_dimensions(str, height_p, width_p, ascent_adjust, x_offset_p)
+
+char *str;                     /* a circled string */
+float *height_p;               /* if non-zero, return circled height here */
+float *width_p;                        /* if non-zero, return circled width here */
+float *ascent_adjust;          /* if non-zero, return amount we added to
+                                * ascent to bring up to minimum height */
+float *x_offset_p;             /* if non-zero, return where to print the
+                                * actual string relative to circle edge */
+
+{
+       int font, size;
+       float min_height;
+       float adjust;                   /* amount to bring up to min height */
+       float uncirc_height, uncirc_width;/* dimensions of uncircled str */
+       float circ_height;              /* height including circle */
+       float circ_width;               /* width including circle */
+       float circ_extra;               /* how much to add to top and
+                                        * bottom to allow space for circle */
+
+
+       /* temporarily make the string uncircled */
+       size = str[2] = str[1];
+       font = str[1] = str[0];
+       /* Note that there is at least one circumstance (in split_string())
+        * where a circled string is temporarily lacking the trailing END_CIR,
+        * and strheight and strwidth don't need it, so we don't need
+        * to blank that out. */
+
+       /* get the dimensions of the uncircled version */
+       uncirc_height = strheight(str+1);
+       uncirc_width = strwidth(str+1);
+
+       /* put the circle back */
+       str[1] = str[2];
+       str[2] = (char) STR_CIR;
+
+       /* If string is unusually short vertically, treat as at least as tall
+        * as the font's ascent. That way if there are a bunch of
+        * circled things and one is tiny, like a dot, that circle
+        * won't be vastly smaller than the others. */
+       min_height = fontascent(font, size);
+       if (uncirc_height < min_height) {
+               adjust = min_height - uncirc_height;
+               uncirc_height = min_height;
+       }
+       else {
+               adjust = 0.0;
+       }
+
+       /* Allow 25% of the height above and below as space for the circle. */
+       circ_extra = 0.25 * uncirc_height;
+       circ_height = 2.0 * circ_extra + uncirc_height;
+
+       /* If width is up to 110% of the height, use the circled
+        * height as the circled width as well. */
+       if (uncirc_width <= 1.1 * uncirc_height) {
+               circ_width = circ_height;
+       }
+       else {
+               /* make a little taller to compensate for the width */
+               circ_extra += circ_height * .03 * (uncirc_width / uncirc_height);
+               circ_height = 2.0 * circ_extra + uncirc_height;
+
+               /* Use 50% of the circled height as the amount to add
+                * to the width, 25% on each end. */
+               circ_width = uncirc_width + 0.5 * circ_height;
+       }
+       if (height_p != 0) {
+               *height_p = circ_height;
+       }
+       if (width_p != 0) {
+               *width_p = circ_width;
+       }
+       if (x_offset_p != 0) {
+               *x_offset_p = (circ_width - uncirc_width) / 2.0;
+       }
+       if (ascent_adjust != 0) {
+               *ascent_adjust = adjust;
+       }
+
+       return(circ_extra);
+}
+\f
+
+/* Return proper version of rehearsal mark string, based on staff number.
+ * It may be circled, boxed, or plain. If circled or boxed, a new string
+ * is returned. If plain, the string is returned as is.
+ */
+
+char *
+get_reh_string(string, staffnum)
+
+char *string;  /* the plain rehearsal mark string */
+int staffnum;  /* which staff it is for */
+
+{
+       char reh_buffer[100];   /* if not okay as it is, copy is put here */
+       int style;
+
+       style = svpath(staffnum, REHSTYLE)->rehstyle;
+
+       if (style == RS_PLAIN) {
+               return(string);
+       }
+
+       if (strlen(string) + 3 > sizeof(reh_buffer)) {
+               /* Usually reh marks are very short,
+                * so if this one is really long, too bad.
+                */
+               ufatal("rehearsal mark is too long");
+       }
+
+       (void) sprintf(reh_buffer, "%c%s%c",
+               style == RS_CIRCLED ? STR_CIR : STR_BOX,
+               string + 2,
+               style == RS_CIRCLED ? STR_CIR_END : STR_BOX_END);
+       return(copy_string(reh_buffer, string[0], string[1]));
+}
+\f
+
+/* Map STR_MUS_CHAR* to FONT_MUSIC*  */
+
+int
+str2mfont(str)
+
+int str;       /* STR_MUS_CHAR*  */
+
+{
+       switch (str) {
+       case STR_MUS_CHAR:
+               return(FONT_MUSIC);
+       case STR_MUS_CHAR2:
+               return(FONT_MUSIC2);
+       default:
+               pfatal("impossible str 0x%x in str2mfont", str);
+               /*NOTREACHED*/
+               return(FONT_MUSIC);
+       }
+}
+
+/* Map FONT_MUSIC* to STR_MUS_CHAR*  */
+
+int
+mfont2str(mfont)
+
+int mfont;     /* FONT_MUSIC*  */
+
+{
+       switch (mfont) {
+       case FONT_MUSIC:
+               return(STR_MUS_CHAR);
+       case FONT_MUSIC2:
+               return(STR_MUS_CHAR2);
+       default:
+               pfatal("impossible mfont %d in mfont2str", mfont);
+               /*NOTREACHED*/
+               return(STR_MUS_CHAR);
+       }
+}