X-Git-Url: https://git.distorted.org.uk/~mdw/mup/blobdiff_plain/cdb3c0882392596f814cf939cbfbd38adc6f2bfe..ddf6330b56bcfb657e0186b24b9b1422c51d3424:/mup/mup/charinfo.c diff --git a/mup/mup/charinfo.c b/mup/mup/charinfo.c new file mode 100644 index 0000000..7f010f4 --- /dev/null +++ b/mup/mup/charinfo.c @@ -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 +#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)); + + +/* 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) ); +} + + +/* 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) ); +} + + +/* 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) ); +} + + +/* 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) ); +} + + +/* 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); +} + + +/* 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); +} + + +/* 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); +} + + +/* 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)); +} + + +/* 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 + + +/* 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 + + +/* 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 + + +/* 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 + + +/* 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); +} + + +/* 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); +} + + +/* 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); +} + + +/* 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)); +} + + +/* 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); +} + + +/* 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); + } +} + + +/* 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); +} + + +/* 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); +} + + +/* 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); +} + + +/* 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); +} + + +/* 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); +} + + +/* 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); +} + + +/* 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])); +} + +/* 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); +} + + +/* 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); +} + + +/* 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); +} + + +/* 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) ] ); + } +} + + +/* 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); +} + + +/* 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') { + ; + } +} + + +/* 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); +} + + +/* 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); +} + + +/* + * 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); +} + + +/* 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); +} + + +/* 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); +} + + +/* 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); +} + + +/* 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])); +} + + +/* 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); + } +}