chiark / gitweb /
Merge branch 'arkkra' into shiny
[mup] / mup / mup / nxtstrch.c
diff --git a/mup/mup/nxtstrch.c b/mup/mup/nxtstrch.c
new file mode 100644 (file)
index 0000000..44be372
--- /dev/null
@@ -0,0 +1,938 @@
+/* Copyright (c) 1999, 2000, 2002, 2004, 2005 by Arkkra Enterprises */
+/* All rights reserved */
+
+/* Functions related to walking through strings, to get each character
+ * in turn, along with any font/size changes and horizontal/vertical
+ * motions that should be done before printing that character.
+ */
+
+#include "defines.h"
+#include "structs.h"
+#include "globals.h"
+
+
+/* when entering pile, adjust size by PILE_A / PILE_B and when
+ * exiting pile, adjust by the recipocal of that. */
+#define PILE_A         65
+#define PILE_B 100
+
+
+/* This structure contains information about one line in a set of lines
+ * that are piled on top of one another. This might be used for figured
+ * bass or superscript/subscript or things like that. */
+struct PILED_LINE {
+       char *text;             /* points to start of piled line */
+       short align_offset;     /* where the alignment is, relative to text */
+       short length;           /* bytes in this line */
+       short last_char_offset; /* where the last character is */
+       short last_digit_offset;/* where the last digit is */
+       float width_left_of_align;      /* width for this line */
+       float width_right_of_align;
+       struct PILED_LINE *next;/* the next line in the pile */
+};
+
+/* This keep track of an entire piled section of a string */
+struct PILED {
+       float curr_x, curr_y;   /* where we are now, relative to pile start */
+       struct PILED_LINE *lines_p;     /* list of lines in pile */
+       struct PILED_LINE *curr_line_p; /* which piled line we're processing */
+       short curr_offset;              /* offset into curr_line_p->text */
+       float width_left_of_align;      /* width of the entire pile */
+       float width_right_of_align;
+       short orig_font;                /* font coming into the pile */
+       short orig_size;                /* size coming into the pile */
+       short new_size;                 /* size at beginning of pile */
+       char *pile_end;                 /* last character of pile */
+};
+
+/* One of these structs is passed to get_piled() with the current
+ * font and size filled in. If we are in piled mode, it will return 1 and
+ * will have filled in all the field. Otherwise it will return 0.
+ */
+
+struct PILE_INFO {
+       char    font;
+       char    size;
+       char    ch;     /* the next character in the pile, or \0 if
+                        * this only indicates motion */
+       double  hor;    /* how much to adjust before printing ch */
+       double  vert;   /* how much to adjust before printing ch */
+};
+
+/* When a backspace is returned, this keeps track of its width, to
+ * potentially be returned via backsp_width() */
+static int Backspace_dist;
+
+/* This is all the info about the piled section we are currently processing.
+ * We only deal with one pile at a time, so a single static structure
+ * meets our needs. */
+static struct PILED Pile_info;
+
+static void begin_pile P((struct PILE_INFO *pile_info_p));
+static char *prep_pile P((char *string, struct PILE_INFO *pile_info_p));
+static int get_piled P((struct PILE_INFO *pile_info_p));
+static double vert_distance P((int vert_arg, int font, int size));
+\f
+
+/* When a STR_PILE is encountered, this should be called to gather all
+ * the information we might need about how to align everything in the pile.
+ * Then other functions like get_piled() can be called to retrieve info.
+ * The passed-in pile_info_p contains the font and size at the beginning
+ * of the pile, and on return will contain the new size and the vertical
+ * and horizontal offsets before the first character of the pile.
+ * Return value points to the last character that was processed from string.
+ */
+
+static char *
+prep_pile(string, pile_info_p)
+
+char *string;          /* place in string where piling begins */
+struct PILE_INFO *pile_info_p; /* passed in font and size, returns size and vert */
+
+{
+       struct PILED_LINE **piled_line_p_p;     /* where to link on next line */
+       struct PILED_LINE *line_p;              /* the current line of pile */
+       char *s;                                /* to walk through string */
+       int alignments = 0;                     /* how many align points found */
+       int lines = 0;                          /* how many lines in pile */
+       float width_contribution;               /* width of current char */
+       int i;                                  /* index though a line */
+       int orig_i;                             /* value of i when current
+                                                * character began (some
+                                                * things take up multiple
+                                                * bytes and we need to know
+                                                * where it began. */
+       int font, size;
+
+       /* initialize */
+       Pile_info.width_left_of_align = Pile_info.width_right_of_align = 0.0;
+       Pile_info.lines_p = (struct PILED_LINE *) 0;
+       Pile_info.curr_line_p = (struct PILED_LINE *) 0;
+       Pile_info.curr_offset = 0;
+       piled_line_p_p = &(Pile_info.lines_p);
+       line_p = *piled_line_p_p;
+
+       Pile_info.orig_font = font = pile_info_p->font;
+
+       /* Adjust size for entering the pile */
+       size = Pile_info.new_size = pile_size(pile_info_p->size, NO);
+
+       /* Walk through the piled things, skipping past the initial STR_PILE,
+        * this time just to find the line boundaries and alignment offsets. */
+       for (s = string + 1; *s != '\0'; s++) {
+
+               /* if end piling, jump out */
+               int curr_char;  
+               curr_char = *s & 0xff;
+               if ( curr_char == STR_PILE || curr_char == STR_BOX_END
+                                               || curr_char == STR_CIR_END) {
+                       if (line_p != (struct PILED_LINE *) 0) {
+                               line_p->length = s - line_p->text;
+                       }
+                       line_p = (struct PILED_LINE *) 0;
+                       s++;
+                       break;
+               }
+
+               /* If we don't have a current PILED_LINE struct,
+                * get one, and initialize it. */
+               if (line_p == (struct PILED_LINE *) 0) {
+                       MALLOC(PILED_LINE, *piled_line_p_p, 1);
+                       line_p = *piled_line_p_p;
+                       line_p->text = s;
+                       line_p->align_offset = -1;
+                       line_p->last_digit_offset = -1;
+                       line_p->last_char_offset = -1;
+                       line_p->length = -1;
+                       line_p->width_left_of_align = 0.0;
+                       line_p->width_right_of_align = 0.0;
+                       line_p->next = (struct PILED_LINE *) 0;
+                       lines++;
+               }
+
+               switch (*s & 0xff) {
+
+               case STR_C_ALIGN:
+               case STR_L_ALIGN:
+                       /* Point the offset at the following character,
+                        * because for C_ALIGN that character will straddle
+                        * the alignment point, and we can easily make
+                        * L_ALIGN work with that convention too. */
+                       line_p->align_offset = s - line_p->text + 1;
+                       alignments++;
+                       break;
+
+               case '\n':
+                       /* if newline, finish this line by saving its length,
+                        * then prepare for next line by moving the place at
+                        * which to add a line to the current line's 'next' */
+                       line_p->length = s - line_p->text;
+                       piled_line_p_p = &(line_p->next);
+                       line_p = (struct PILED_LINE *) 0;
+                       break;
+
+               case STR_FONT:
+               case STR_SIZE:
+               case STR_BACKSPACE:
+               case STR_VERTICAL:
+                       /* need to skip past its argument byte */
+                       s++;
+                       break;
+
+               case STR_MUS_CHAR:
+               case STR_MUS_CHAR2:
+                       /* Mark as last character so far, in case we need
+                        * to use that as default alignment */
+                       line_p->last_char_offset = s - line_p->text;
+                       /* need to skip past its 2 argument bytes */
+                       s += 2;
+                       break;
+
+               case STR_PAGENUM:
+               case STR_NUMPAGES:
+                       /* We actually don't allow this, but can get to this
+                        * code in error cases. Skip past argument. */
+                       s++;
+                       break;
+
+               default:
+                       /* Mark as last character so far, in case we need
+                        * to use that as default alignment */
+                       line_p->last_char_offset = s - line_p->text;
+
+                       /* remember location of last digit, in case we
+                        * need to use as default aligment */
+                       if ( isdigit(*s) ) {
+                               line_p->last_digit_offset = s - line_p->text;
+                       }
+                       break;
+               }
+       }
+
+       if (*s == '\0' && line_p != (struct PILED_LINE *) 0) {
+               line_p->length = s - line_p->text;
+       }
+
+       /* if there are lines with unspecified alignment points, find them */
+       if (alignments < lines) {
+               for (line_p = Pile_info.lines_p;
+                               line_p != (struct PILED_LINE *) 0;
+                               line_p = line_p->next) {
+                       /* If no user-specified alignment point in this line,
+                        * use last digit if any, else last char. If there was
+                        * no last char (empty line, maybe to get subscript
+                        * without superscript, for example) this will end
+                        * up with an offset of -1. */
+                       if (line_p->align_offset < 0) {
+                               line_p->align_offset =
+                                   ((line_p->last_digit_offset >= 0)
+                                   ? line_p->last_digit_offset
+                                   : line_p->last_char_offset);
+                       }
+               }
+       }
+
+       /* Find left and right of alignment for each line and the
+        * widest overall for the pile. */
+       for (line_p = Pile_info.lines_p;
+                                       line_p != (struct PILED_LINE *) 0;
+                                       line_p = line_p->next) {
+
+               /* Some items take more than one byte. In that case,
+                * the index 'i' in this 'for' loop will get more added to it
+                * than just the increment at the end of each loop.
+                * When this happens, orig_i keeps track of where the
+                * multi-byte item began. */
+               for (i = 0; i < line_p->length; i++) {
+                       
+                       orig_i = i;
+                       width_contribution = 0.0;
+
+                       switch (line_p->text[i] & 0xff) {
+
+                       case STR_MUS_CHAR:
+                       case STR_MUS_CHAR2:
+                               /* get its width and skip past its args */
+                               width_contribution += width(
+                                               str2mfont(line_p->text[i] & 0xff),
+                                               line_p->text[i+1],
+                                               line_p->text[i+2]);
+                               i += 2;
+                               break;
+
+                       case STR_FONT:
+                               /* Remember the font, may need for getting
+                                * widths of future characters */
+                               font = line_p->text[++i];
+                               break;
+
+                       case STR_SIZE:
+                               /* remember the size, may need for getting
+                                * widths of future characters */
+                               size = line_p->text[++i];
+                               break;
+
+                       case STR_C_ALIGN:
+                       case STR_L_ALIGN:
+                       case STR_SLASH:
+                               /* irrelevant for width, no args */
+                               break;
+
+                       case STR_VERTICAL:
+                               /* irrelevant for width, 1 byte arg to skip */
+                               i++;
+                               break;
+
+                       case STR_BACKSPACE:
+                               width_contribution -= ((line_p->text[++i] *
+                                       size) / DFLT_SIZE) / BACKSP_FACTOR;
+                               break;
+
+                       case STR_BOX:
+                       case STR_BOX_END:
+                       case STR_CIR:
+                       case STR_CIR_END:
+                               /* not relevant here */
+                               break;
+
+                       case STR_PAGENUM:
+                       case STR_NUMPAGES:
+                               /* we don't really know how wide this will
+                                * end up being, so just use the width of
+                                * the following byte (% or #).
+                                */
+                               width_contribution = width(font, size,
+                                                line_p->text[++i]);
+                               break;
+                       default:
+                               if ( IS_STR_COMMAND(line_p->text[i]) & 0xff) {
+                                       pfatal("unexpected STR_ value 0x%x in prep_pile()",
+                                               line_p->text[i] & 0xff);
+                               }
+
+                               /* ordinary character, look up its width */
+                               width_contribution = width(font, size,
+                                               line_p->text[i]);
+                               break;
+                       }
+
+                       /* Figure out which side of alignment.
+                        * Anything before align_offset goes on left,
+                        * while anything after goes on the right.
+                        * If we're right at the alignment offset,
+                        * and the alignment type is STR_C_ALIGN,
+                        * split the width. Otherwise goes on right. */
+                       if (orig_i < line_p->align_offset) {
+                               line_p->width_left_of_align
+                                       += width_contribution;
+                       }
+                       else if (orig_i == line_p->align_offset &&
+                                       (line_p->text[orig_i - 1] & 0xff)
+                                       != STR_L_ALIGN) {
+                               /* put half on each side */
+                               width_contribution /= 2.0;
+                               line_p->width_left_of_align
+                                       += width_contribution;
+                               line_p->width_right_of_align
+                                       += width_contribution;
+                       }
+                       else {
+                               line_p->width_right_of_align
+                                       += width_contribution;
+                       }
+               }
+
+               /* find widest line */
+               if (line_p->width_left_of_align
+                                       > Pile_info.width_left_of_align) {
+                       Pile_info.width_left_of_align
+                                               = line_p->width_left_of_align;
+               }
+               if (line_p->width_right_of_align
+                                       > Pile_info.width_right_of_align) {
+                       Pile_info.width_right_of_align
+                                               = line_p->width_right_of_align;
+               }
+       }
+
+       /* start at first character of first line of pile */
+       begin_pile(pile_info_p);
+
+       Pile_info.pile_end = s;
+       return(s);
+}
+\f
+
+/* fill in the fields of pile_info_p for starting at the beginning of
+ * the current pile. */
+
+static void
+begin_pile(pile_info_p)
+
+struct PILE_INFO *pile_info_p;
+
+{
+       /* begin at first line of pile */
+       Pile_info.curr_line_p = Pile_info.lines_p;
+       Pile_info.curr_offset = -1;
+
+       /* if user ended a string with a pile indicator (which is a bit
+        * silly, but could happen...) get out now,
+        * to avoid dereferencing a line that doesn't exist. */
+       if (Pile_info.curr_line_p == (struct PILED_LINE *) 0) {
+               return;
+       }
+
+       /* fill in rest of returned data */
+       pile_info_p->hor = Pile_info.curr_x = Pile_info.width_left_of_align
+                               - Pile_info.lines_p->width_left_of_align;
+       /* Adjust vertical by half the unpiled line height, but less a little
+        * (there is some white space padding, usually piles are numbers
+        * without descenders, and they are smaller anyway, so looks better
+        * to jam things together a bit more than normal). */
+       pile_info_p->vert = Pile_info.curr_y
+               = fontheight(Pile_info.orig_font, Pile_info.orig_size) / 2.0 
+               - POINT * Pile_info.new_size / DFLT_SIZE;
+       pile_info_p->size = Pile_info.new_size;
+
+       pile_info_p->ch = '\0';
+}
+\f
+
+/* Get the next character in piled segment, along with any horizontal
+ * and/or vertical motion that should be done before printing that
+ * character. Info is placed into the struct passed in. If the returned
+ * ->ch is '\0', that means this call only returned motion information.
+ * Returns NO at end of pile, otherwise YES.
+ */
+
+static int
+get_piled(pile_info_p)
+
+struct PILE_INFO *pile_info_p;
+
+{
+       char *text = 0;         /* Piled text to process. The initialization
+                                * is just to avoid bogus "used before set"
+                                * warning. It will always get set to a real
+                                * string before actually being used. */
+       float backdist;         /* width of backspace */
+       int mfont;              /* FONT_MUSIC*  */
+
+
+       /* Move on to next character. */
+       ++(Pile_info.curr_offset);
+
+       if (Pile_info.curr_line_p != (struct PILED_LINE *) 0) {
+
+               /* Check if we've gotten to end of pile. We are if we're
+                * past the last character of the last line of the pile */
+               if (Pile_info.curr_line_p->next == (struct PILED_LINE *) 0 &&
+                               Pile_info.curr_offset
+                               >= Pile_info.curr_line_p->length) {
+                       /* yes, at end of pile */
+                       Pile_info.curr_line_p = (struct PILED_LINE *) 0;
+               }
+               else {
+                       text = Pile_info.curr_line_p->text;
+               }
+       }
+
+       /* check for end of pile */
+       if (Pile_info.curr_line_p == (struct PILED_LINE *) 0) {
+
+               /* Adjust vertical and horizontal to past pile.
+                * Horizontal adjust is the total width of the
+                * pile minus where we are currently. */
+               pile_info_p->hor = (Pile_info.width_left_of_align
+                                       + Pile_info.width_right_of_align)
+                                       - Pile_info.curr_x;
+               /* Vertical adjust is the negative of the current
+                * y, since that gets us back to zero relative to
+                * where we started. */
+               pile_info_p->vert = - (Pile_info.curr_y);
+
+               /* Restore size for leaving the pile. */
+               pile_info_p->size = pile_size(pile_info_p->size, YES);
+
+               /* mark as not being an actual character, just motion */
+               pile_info_p->ch = '\0';
+
+               return(NO);
+       }
+
+       /* handle newline -- go to next line of pile */
+       if (text[Pile_info.curr_offset] == '\n') {
+               /* prepare for next line, if any */
+               Pile_info.curr_line_p = Pile_info.curr_line_p->next;
+               Pile_info.curr_offset = -1;
+
+               /* Adjust vertical and horizontal to start next line of pile.
+                * For horizontal, go back to beginning of pile,
+                * then add the difference between this line's
+                * left of align and the pile's. */
+               if (Pile_info.curr_line_p != (struct PILED_LINE *) 0) {
+                       pile_info_p->hor = - Pile_info.curr_x;
+                       Pile_info.curr_x = Pile_info.width_left_of_align
+                               - Pile_info.curr_line_p->width_left_of_align;
+                       pile_info_p->hor += Pile_info.curr_x;
+
+                       /* vertical is adjusted by height of font, less
+                        * a little to jam together to look better. */
+                       pile_info_p->vert = - fontheight(pile_info_p->font,
+                               pile_info_p->size)
+                               + POINT * pile_info_p->size / DFLT_SIZE;
+                       Pile_info.curr_y += pile_info_p->vert;
+               }
+               else {
+                       pile_info_p->vert = pile_info_p->hor = 0.0;
+               }
+
+               /* only motion */
+               pile_info_p->ch = '\0';
+
+       }
+
+       else {
+               pile_info_p->hor = pile_info_p->vert = 0.0;
+
+               switch (text[Pile_info.curr_offset] & 0xff) {
+
+               case STR_MUS_CHAR:
+               case STR_MUS_CHAR2:
+                       mfont = str2mfont( text[Pile_info.curr_offset] & 0xff);
+                       pile_info_p->font = mfont;
+                       pile_info_p->size = text[Pile_info.curr_offset+1];
+                       pile_info_p->ch = text[Pile_info.curr_offset+2];
+                       Pile_info.curr_x += width(mfont,
+                                       pile_info_p->size, pile_info_p->ch);
+                       Pile_info.curr_offset += 2;
+                       break;
+
+               case STR_FONT:
+                       (Pile_info.curr_offset)++;
+                       pile_info_p->font = text[Pile_info.curr_offset];
+                       pile_info_p->ch = '\0';
+                       break;
+
+               case STR_SIZE:
+                       (Pile_info.curr_offset)++;
+                       pile_info_p->size = text[Pile_info.curr_offset];
+                       pile_info_p->ch = '\0';
+                       break;
+
+               case STR_SLASH:
+                       pile_info_p->ch = (char) STR_SLASH;
+                       break;
+
+               case STR_C_ALIGN:
+               case STR_L_ALIGN:
+                       pile_info_p->ch = '\0';
+                       break;
+
+               case STR_VERTICAL:
+                       (Pile_info.curr_offset)++;
+                       pile_info_p->vert = vert_distance(
+                               DECODE_VERT(text[Pile_info.curr_offset]),
+                               pile_info_p->font, pile_info_p->size);
+                       pile_info_p->ch = '\0';
+                       break;
+
+               case STR_BACKSPACE:
+                       (Pile_info.curr_offset)++;
+                       Backspace_dist = text[Pile_info.curr_offset];
+                       backdist = backsp_width(pile_info_p->size);
+                       pile_info_p->hor -= backdist;
+                       Pile_info.curr_x -= backdist;
+                       pile_info_p->ch = '\0';
+                       break;
+
+               case STR_PAGENUM:
+               case STR_NUMPAGES:
+                       /* These aren't really allowed in piles,
+                        * but we can hit this code under some error cases,
+                        * so we recognize them and do something reasonable.
+                        */
+                       (Pile_info.curr_offset)++;
+                       pile_info_p->ch = text[Pile_info.curr_offset];
+                       break;
+               
+               default:
+                       if ( IS_STR_COMMAND(text[Pile_info.curr_offset]) & 0xff) {
+                               pfatal("unexpected STR_ value 0x%x in get_piled()",
+                                       text[Pile_info.curr_offset] & 0xff);
+                       }
+
+                       /* ordinary character */
+                       Pile_info.curr_x += width(pile_info_p->font,
+                                       pile_info_p->size,
+                                       text[Pile_info.curr_offset]);
+                       pile_info_p->ch = text[Pile_info.curr_offset];
+                       break;
+               }
+       }
+
+       return(YES);
+}
+\f
+
+/* given a pointer into a string, return the code for the next
+ * character in the string. This will be either the ASCII code
+ * or the music symbol code. This function will also update the
+ * pointer into the string and the font and size if appropriate.
+ * If there happens to be a music character in the middle of a string,
+ * the font (and maybe size) will temporarily be changed for the music
+ * font. This function keeps track of that.
+ *
+ * Restrictions: This function must be used by calling it on the beginning
+ * of a string, then to walk through that string. You don't have to go all the
+ * way to the end of a given string, but once you switch to a new string
+ * (from which you must begin at its beginning) you cannot go back to the
+ * middle of some other string that you had been working on before.
+ *
+ * This function calls the more general function which can also update
+ * vertical motion and alignment information. It is retained for backward
+ * compatibility for the places that don't care about those things.
+ */
+
+int
+next_str_char(str_p_p, font_p, size_p)
+
+char **str_p_p;        /* address of pointer into string. Will be incremented
+                * to beyond current character on return (this might
+                * be several bytes into the string if current char
+                * is a special string command */
+int *font_p;   /* pointer to font. Will be updated to new font if
+                * appropriate */
+int *size_p;   /* pointer to size. Will be updated to new size if
+                * appropriate */
+
+{
+       /* place to hold things we don't care about from general function */
+       double dummy_vertical, dummy_horizontal;
+       int dummy_in_pile;
+       int dummy_textfont;
+
+       return(nxt_str_char(str_p_p, font_p, size_p, &dummy_textfont,
+               &dummy_vertical, &dummy_horizontal, &dummy_in_pile, NO));
+}
+\f
+
+/* returns the next character in string, along with any font/size changes
+ * and horizontal/vertical motions that should happen before that character.
+ */
+
+int
+nxt_str_char(str_p_p, font_p, size_p, textfont_p, vertical_p, horizontal_p, in_pile_p, slash)
+
+char **str_p_p;
+int *font_p;
+int *size_p;
+int *textfont_p;       /* font disregarding music characters */
+double *vertical_p;    /* if non-zero, do vertical motion */
+double *horizontal_p;  /* if non-zero, do horizontal motion */
+int *in_pile_p;                /* will be set to YES if inside a pile */
+int slash;             /* if YES, return any STR_SLASH's found,
+                        * else ignore them */
+
+{
+       static char *prev_str_p;        /* most recent value of *str_p_p */
+       static int textfont;            /* current font, ignoring music chars */
+       static int textsize;            /* current size, ignoring music chars */
+       static int pile_mode;           /* if inside a pile */
+       int code;
+       struct PILE_INFO pile_info;
+
+       /* initialize to assume no vertical motion or alignment at this char */
+       *horizontal_p = *vertical_p = 0.0;
+
+       if (*str_p_p == (char *) 0) {
+               return(0);
+       }
+
+       for ( ; ; ) {
+
+               if (prev_str_p != *str_p_p) {
+                       /* must be starting on a different string. Our
+                        * remembered text font/size are no longer valid */
+                       textfont = *font_p;
+                       *textfont_p = textfont;
+                       textsize = *size_p;
+                       pile_mode = NO;
+               }
+
+               if (pile_mode == YES) {
+                       /* call the special function for piled to get next */
+                       pile_info.font = textfont;
+                       pile_info.size = textsize;
+                       pile_mode = get_piled(&pile_info);
+
+                       /* save what it returned */
+                       *font_p = pile_info.font;
+                       *size_p = pile_info.size;
+                       if (pile_info.font != FONT_MUSIC) {
+                               textfont = pile_info.font;
+                               *textfont_p = textfont;
+                               textsize = pile_info.size;
+                       }
+
+                       /* Add motion to any accumulated so far */
+                        *vertical_p += pile_info.vert;
+                        *horizontal_p += pile_info.hor;
+
+                       if (pile_info.ch == '\0') {
+                               /* only motion this time, keep going */
+                               continue;
+                       }
+                       else if ( (pile_info.ch & 0xff) == STR_SLASH &&
+                                               slash == NO) {
+                               /* caller doesn't care about slashes */
+                               continue;
+                       }
+                       else {
+                               *in_pile_p = pile_mode;
+                               return (pile_info.ch);
+                       }
+               }
+               *in_pile_p = NO;
+
+               if (**str_p_p == '\0') {
+                       /* end of string */
+                       *font_p = textfont;
+                       *size_p = textsize;
+                       return(0);
+               }
+
+               if ( ! IS_STR_COMMAND(**str_p_p)) {
+                       /* just an ordinary character. Return it after updating
+                        * pointer to point past it */
+                       code = **str_p_p & 0xff;
+                       ++(*str_p_p);
+                       prev_str_p = *str_p_p;
+
+                       /* if last chacter was music character, put back real                            * font and size */
+                       if (IS_MUSIC_FONT(*font_p)) {
+                               *font_p = textfont;
+                               *size_p = textsize;
+                       }
+                       return(code);
+               }
+
+               /* special command. see which one */
+               switch ( (unsigned) (**str_p_p & 0xff) ) {
+
+               case STR_FONT:
+                       /* change font to specified font and point past it */
+                       *font_p = *++(*str_p_p);
+                       ++(*str_p_p);
+                       textfont = *font_p;
+                       *textfont_p = textfont;
+                       break;
+
+               case STR_SIZE:
+                       /* change size to specified size and point past it */
+                       *size_p = *++(*str_p_p);
+                       ++(*str_p_p);
+                       textsize = *size_p;
+                       break;
+
+               case STR_PAGENUM:
+                       /* Page number and number of pages are strange cases.
+                        * We don't necessarily know at this point
+                        * how many digits long they will be. So we punt:
+                        * just return % or #. For height, ascent, and descent,
+                        * the % or # will be the same as any number
+                        * of digits (or at least close enough, we hope).
+                        * That means the only problem is when we are getting
+                        * the width of a string or actually printing
+                        * it, so we'll deal with those as special cases */
+                       *str_p_p += 2;
+                       prev_str_p = *str_p_p;
+                       return('%');
+               case STR_NUMPAGES:
+                       *str_p_p += 2;
+                       prev_str_p = *str_p_p;
+                       return('#');
+
+
+               case STR_MUS_CHAR:
+               case STR_MUS_CHAR2:
+                       /* music character */
+                       *font_p = str2mfont( (unsigned) (**str_p_p & 0xff) );
+                       *size_p = *++(*str_p_p);
+                       code = (*++(*str_p_p)) & 0xff;
+                       ++(*str_p_p);
+                       prev_str_p = *str_p_p;
+                       return(code);
+
+               case STR_BACKSPACE:
+                       Backspace_dist = (*++(*str_p_p)) & 0xff;
+                       ++(*str_p_p);
+                       prev_str_p = *str_p_p;
+                       *font_p = textfont;
+                       *size_p = textsize;
+                       return('\b');
+
+               case STR_PRE:
+               case STR_U_PRE:
+               case STR_PRE_END:
+               case STR_PST:
+               case STR_U_PST:
+               case STR_PST_END:
+               case STR_BOX:
+               case STR_BOX_END:
+               case STR_CIR:
+               case STR_CIR_END:
+                       ++(*str_p_p);
+                       break;
+
+               case STR_VERTICAL:
+                       *vertical_p += vert_distance(
+                                       DECODE_VERT( *++(*str_p_p) ),
+                                       textfont, textsize);
+                       ++(*str_p_p);
+                       break;
+
+               case STR_C_ALIGN:
+               case STR_L_ALIGN:
+                       ++(*str_p_p);
+                       break;
+
+               case STR_PILE:
+                       pile_info.font = textfont;
+                       pile_info.size = textsize;
+                       *str_p_p = prep_pile(*str_p_p, &pile_info);
+                       *vertical_p += pile_info.vert;
+                       *horizontal_p += pile_info.hor;
+                       *size_p = pile_info.size;
+                       textsize = *size_p;
+                       pile_mode = YES;
+                       break;
+
+               case STR_SLASH:
+                       ++(*str_p_p);
+                       prev_str_p = *str_p_p;
+                       if (slash == YES) {
+                               return(STR_SLASH);
+                       }
+                       break;
+
+               default:
+                       pfatal("bad internal format for string (%x)",
+                                       **str_p_p & 0xff);
+                       /*NOTREACHED*/
+                       break;
+               }
+               prev_str_p = *str_p_p;
+       }
+}
+\f
+
+/* Given an argument to STR_VERTICAL and a font and size,
+ * return the distance in inches to move vertically. */
+
+static double
+vert_distance(vert_arg, font, size)
+
+int vert_arg;
+int font;
+int size;
+
+{
+       double ratio;
+
+       /* first convert to ratio relative to font height */
+       if (vert_arg >= 0) {
+               ratio = (double) vert_arg / (double) MAXVERTICAL;
+       }
+       else {
+               ratio = (double) vert_arg / (double) (-MINVERTICAL);
+       }
+       return(ratio * fontheight(font, size));
+}
+\f
+
+/* return width of most recent backspace returned by next_str_char() */
+
+double
+backsp_width(size)
+
+int size;      /* adjust to this point size */
+
+{
+       return ( (double) Backspace_dist * ((double) size / (double) DFLT_SIZE)
+                                       / BACKSP_FACTOR);
+}
+
+
+/* Return width of current pile */
+
+double
+pile_width()
+
+{
+       return(Pile_info.width_left_of_align + Pile_info.width_right_of_align);
+}
+\f
+
+/* adjust size when entering/leaving a pile. Called with current size and
+ * whether we are entering or leaving. It returns the new size. */
+
+int
+pile_size(size, in_pile)
+
+int size;      /* current size */
+int in_pile;   /* if YES, we are currently in a pile and are about to leave
+                * it; if NO, we are about to enter the pile */
+
+{
+       int new_size;
+
+       if (in_pile == NO) {
+               /* entering pile. Save current size, and return new size */
+               Pile_info.orig_size = size;
+               /* constrain new size to be at least MINSIZE */
+               if ((Pile_info.new_size = PILE_A * size / PILE_B) < MINSIZE) {
+                       Pile_info.new_size = MINSIZE;
+               }
+               return(Pile_info.new_size);
+       }
+       else {
+               /* leaving pile */
+               /* Restore size. If we're still at the size at the beginning
+                * of the pile, use the orginal value before the pile to avoid
+                * any possible roundoff errors going back and forth,
+                * otherwise adjust by inverse of the factor used when
+                * entering the pile. */
+               if (size == Pile_info.new_size) {
+                       return(Pile_info.orig_size);
+               }
+               else {
+                       if ((new_size = (size * PILE_B) / PILE_A) > MAXSIZE) {
+                               /* constrain to no larger than MAXSIZE */
+                               new_size = MAXSIZE;
+                       }
+                       return(new_size);
+               }
+       }
+}
+\f
+
+/* given where a pile begins and the font/size at that point, return the
+ * width from the beginning to the pile to the alignment point of the pile. */
+
+double
+align_distance(string, font, size)
+
+char *string;  /* pile starts here */
+int font;
+int size;
+
+{
+       struct PILE_INFO pile_info;
+
+       pile_info.font = font;
+       pile_info.size = size;
+       (void) prep_pile(string, &pile_info);
+       return(Pile_info.width_left_of_align);
+}