--- /dev/null
+
+/* 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);
+ }
+}