1 /* Copyright (c) 1999, 2000, 2002, 2004, 2005 by Arkkra Enterprises */
2 /* All rights reserved */
4 /* Functions related to walking through strings, to get each character
5 * in turn, along with any font/size changes and horizontal/vertical
6 * motions that should be done before printing that character.
14 /* when entering pile, adjust size by PILE_A / PILE_B and when
15 * exiting pile, adjust by the recipocal of that. */
20 /* This structure contains information about one line in a set of lines
21 * that are piled on top of one another. This might be used for figured
22 * bass or superscript/subscript or things like that. */
24 char *text; /* points to start of piled line */
25 short align_offset; /* where the alignment is, relative to text */
26 short length; /* bytes in this line */
27 short last_char_offset; /* where the last character is */
28 short last_digit_offset;/* where the last digit is */
29 float width_left_of_align; /* width for this line */
30 float width_right_of_align;
31 struct PILED_LINE *next;/* the next line in the pile */
34 /* This keep track of an entire piled section of a string */
36 float curr_x, curr_y; /* where we are now, relative to pile start */
37 struct PILED_LINE *lines_p; /* list of lines in pile */
38 struct PILED_LINE *curr_line_p; /* which piled line we're processing */
39 short curr_offset; /* offset into curr_line_p->text */
40 float width_left_of_align; /* width of the entire pile */
41 float width_right_of_align;
42 short orig_font; /* font coming into the pile */
43 short orig_size; /* size coming into the pile */
44 short new_size; /* size at beginning of pile */
45 char *pile_end; /* last character of pile */
48 /* One of these structs is passed to get_piled() with the current
49 * font and size filled in. If we are in piled mode, it will return 1 and
50 * will have filled in all the field. Otherwise it will return 0.
56 char ch; /* the next character in the pile, or \0 if
57 * this only indicates motion */
58 double hor; /* how much to adjust before printing ch */
59 double vert; /* how much to adjust before printing ch */
62 /* When a backspace is returned, this keeps track of its width, to
63 * potentially be returned via backsp_width() */
64 static int Backspace_dist;
66 /* This is all the info about the piled section we are currently processing.
67 * We only deal with one pile at a time, so a single static structure
69 static struct PILED Pile_info;
71 static void begin_pile P((struct PILE_INFO *pile_info_p));
72 static char *prep_pile P((char *string, struct PILE_INFO *pile_info_p));
73 static int get_piled P((struct PILE_INFO *pile_info_p));
74 static double vert_distance P((int vert_arg, int font, int size));
77 /* When a STR_PILE is encountered, this should be called to gather all
78 * the information we might need about how to align everything in the pile.
79 * Then other functions like get_piled() can be called to retrieve info.
80 * The passed-in pile_info_p contains the font and size at the beginning
81 * of the pile, and on return will contain the new size and the vertical
82 * and horizontal offsets before the first character of the pile.
83 * Return value points to the last character that was processed from string.
87 prep_pile(string, pile_info_p)
89 char *string; /* place in string where piling begins */
90 struct PILE_INFO *pile_info_p; /* passed in font and size, returns size and vert */
93 struct PILED_LINE **piled_line_p_p; /* where to link on next line */
94 struct PILED_LINE *line_p; /* the current line of pile */
95 char *s; /* to walk through string */
96 int alignments = 0; /* how many align points found */
97 int lines = 0; /* how many lines in pile */
98 float width_contribution; /* width of current char */
99 int i; /* index though a line */
100 int orig_i; /* value of i when current
101 * character began (some
102 * things take up multiple
103 * bytes and we need to know
108 Pile_info.width_left_of_align = Pile_info.width_right_of_align = 0.0;
109 Pile_info.lines_p = (struct PILED_LINE *) 0;
110 Pile_info.curr_line_p = (struct PILED_LINE *) 0;
111 Pile_info.curr_offset = 0;
112 piled_line_p_p = &(Pile_info.lines_p);
113 line_p = *piled_line_p_p;
115 Pile_info.orig_font = font = pile_info_p->font;
117 /* Adjust size for entering the pile */
118 size = Pile_info.new_size = pile_size(pile_info_p->size, NO);
120 /* Walk through the piled things, skipping past the initial STR_PILE,
121 * this time just to find the line boundaries and alignment offsets. */
122 for (s = string + 1; *s != '\0'; s++) {
124 /* if end piling, jump out */
126 curr_char = *s & 0xff;
127 if ( curr_char == STR_PILE || curr_char == STR_BOX_END
128 || curr_char == STR_CIR_END) {
129 if (line_p != (struct PILED_LINE *) 0) {
130 line_p->length = s - line_p->text;
132 line_p = (struct PILED_LINE *) 0;
137 /* If we don't have a current PILED_LINE struct,
138 * get one, and initialize it. */
139 if (line_p == (struct PILED_LINE *) 0) {
140 MALLOC(PILED_LINE, *piled_line_p_p, 1);
141 line_p = *piled_line_p_p;
143 line_p->align_offset = -1;
144 line_p->last_digit_offset = -1;
145 line_p->last_char_offset = -1;
147 line_p->width_left_of_align = 0.0;
148 line_p->width_right_of_align = 0.0;
149 line_p->next = (struct PILED_LINE *) 0;
157 /* Point the offset at the following character,
158 * because for C_ALIGN that character will straddle
159 * the alignment point, and we can easily make
160 * L_ALIGN work with that convention too. */
161 line_p->align_offset = s - line_p->text + 1;
166 /* if newline, finish this line by saving its length,
167 * then prepare for next line by moving the place at
168 * which to add a line to the current line's 'next' */
169 line_p->length = s - line_p->text;
170 piled_line_p_p = &(line_p->next);
171 line_p = (struct PILED_LINE *) 0;
178 /* need to skip past its argument byte */
184 /* Mark as last character so far, in case we need
185 * to use that as default alignment */
186 line_p->last_char_offset = s - line_p->text;
187 /* need to skip past its 2 argument bytes */
193 /* We actually don't allow this, but can get to this
194 * code in error cases. Skip past argument. */
199 /* Mark as last character so far, in case we need
200 * to use that as default alignment */
201 line_p->last_char_offset = s - line_p->text;
203 /* remember location of last digit, in case we
204 * need to use as default aligment */
206 line_p->last_digit_offset = s - line_p->text;
212 if (*s == '\0' && line_p != (struct PILED_LINE *) 0) {
213 line_p->length = s - line_p->text;
216 /* if there are lines with unspecified alignment points, find them */
217 if (alignments < lines) {
218 for (line_p = Pile_info.lines_p;
219 line_p != (struct PILED_LINE *) 0;
220 line_p = line_p->next) {
221 /* If no user-specified alignment point in this line,
222 * use last digit if any, else last char. If there was
223 * no last char (empty line, maybe to get subscript
224 * without superscript, for example) this will end
225 * up with an offset of -1. */
226 if (line_p->align_offset < 0) {
227 line_p->align_offset =
228 ((line_p->last_digit_offset >= 0)
229 ? line_p->last_digit_offset
230 : line_p->last_char_offset);
235 /* Find left and right of alignment for each line and the
236 * widest overall for the pile. */
237 for (line_p = Pile_info.lines_p;
238 line_p != (struct PILED_LINE *) 0;
239 line_p = line_p->next) {
241 /* Some items take more than one byte. In that case,
242 * the index 'i' in this 'for' loop will get more added to it
243 * than just the increment at the end of each loop.
244 * When this happens, orig_i keeps track of where the
245 * multi-byte item began. */
246 for (i = 0; i < line_p->length; i++) {
249 width_contribution = 0.0;
251 switch (line_p->text[i] & 0xff) {
255 /* get its width and skip past its args */
256 width_contribution += width(
257 str2mfont(line_p->text[i] & 0xff),
264 /* Remember the font, may need for getting
265 * widths of future characters */
266 font = line_p->text[++i];
270 /* remember the size, may need for getting
271 * widths of future characters */
272 size = line_p->text[++i];
278 /* irrelevant for width, no args */
282 /* irrelevant for width, 1 byte arg to skip */
287 width_contribution -= ((line_p->text[++i] *
288 size) / DFLT_SIZE) / BACKSP_FACTOR;
295 /* not relevant here */
300 /* we don't really know how wide this will
301 * end up being, so just use the width of
302 * the following byte (% or #).
304 width_contribution = width(font, size,
308 if ( IS_STR_COMMAND(line_p->text[i]) & 0xff) {
309 pfatal("unexpected STR_ value 0x%x in prep_pile()",
310 line_p->text[i] & 0xff);
313 /* ordinary character, look up its width */
314 width_contribution = width(font, size,
319 /* Figure out which side of alignment.
320 * Anything before align_offset goes on left,
321 * while anything after goes on the right.
322 * If we're right at the alignment offset,
323 * and the alignment type is STR_C_ALIGN,
324 * split the width. Otherwise goes on right. */
325 if (orig_i < line_p->align_offset) {
326 line_p->width_left_of_align
327 += width_contribution;
329 else if (orig_i == line_p->align_offset &&
330 (line_p->text[orig_i - 1] & 0xff)
332 /* put half on each side */
333 width_contribution /= 2.0;
334 line_p->width_left_of_align
335 += width_contribution;
336 line_p->width_right_of_align
337 += width_contribution;
340 line_p->width_right_of_align
341 += width_contribution;
345 /* find widest line */
346 if (line_p->width_left_of_align
347 > Pile_info.width_left_of_align) {
348 Pile_info.width_left_of_align
349 = line_p->width_left_of_align;
351 if (line_p->width_right_of_align
352 > Pile_info.width_right_of_align) {
353 Pile_info.width_right_of_align
354 = line_p->width_right_of_align;
358 /* start at first character of first line of pile */
359 begin_pile(pile_info_p);
361 Pile_info.pile_end = s;
366 /* fill in the fields of pile_info_p for starting at the beginning of
367 * the current pile. */
370 begin_pile(pile_info_p)
372 struct PILE_INFO *pile_info_p;
375 /* begin at first line of pile */
376 Pile_info.curr_line_p = Pile_info.lines_p;
377 Pile_info.curr_offset = -1;
379 /* if user ended a string with a pile indicator (which is a bit
380 * silly, but could happen...) get out now,
381 * to avoid dereferencing a line that doesn't exist. */
382 if (Pile_info.curr_line_p == (struct PILED_LINE *) 0) {
386 /* fill in rest of returned data */
387 pile_info_p->hor = Pile_info.curr_x = Pile_info.width_left_of_align
388 - Pile_info.lines_p->width_left_of_align;
389 /* Adjust vertical by half the unpiled line height, but less a little
390 * (there is some white space padding, usually piles are numbers
391 * without descenders, and they are smaller anyway, so looks better
392 * to jam things together a bit more than normal). */
393 pile_info_p->vert = Pile_info.curr_y
394 = fontheight(Pile_info.orig_font, Pile_info.orig_size) / 2.0
395 - POINT * Pile_info.new_size / DFLT_SIZE;
396 pile_info_p->size = Pile_info.new_size;
398 pile_info_p->ch = '\0';
402 /* Get the next character in piled segment, along with any horizontal
403 * and/or vertical motion that should be done before printing that
404 * character. Info is placed into the struct passed in. If the returned
405 * ->ch is '\0', that means this call only returned motion information.
406 * Returns NO at end of pile, otherwise YES.
410 get_piled(pile_info_p)
412 struct PILE_INFO *pile_info_p;
415 char *text = 0; /* Piled text to process. The initialization
416 * is just to avoid bogus "used before set"
417 * warning. It will always get set to a real
418 * string before actually being used. */
419 float backdist; /* width of backspace */
420 int mfont; /* FONT_MUSIC* */
423 /* Move on to next character. */
424 ++(Pile_info.curr_offset);
426 if (Pile_info.curr_line_p != (struct PILED_LINE *) 0) {
428 /* Check if we've gotten to end of pile. We are if we're
429 * past the last character of the last line of the pile */
430 if (Pile_info.curr_line_p->next == (struct PILED_LINE *) 0 &&
431 Pile_info.curr_offset
432 >= Pile_info.curr_line_p->length) {
433 /* yes, at end of pile */
434 Pile_info.curr_line_p = (struct PILED_LINE *) 0;
437 text = Pile_info.curr_line_p->text;
441 /* check for end of pile */
442 if (Pile_info.curr_line_p == (struct PILED_LINE *) 0) {
444 /* Adjust vertical and horizontal to past pile.
445 * Horizontal adjust is the total width of the
446 * pile minus where we are currently. */
447 pile_info_p->hor = (Pile_info.width_left_of_align
448 + Pile_info.width_right_of_align)
450 /* Vertical adjust is the negative of the current
451 * y, since that gets us back to zero relative to
452 * where we started. */
453 pile_info_p->vert = - (Pile_info.curr_y);
455 /* Restore size for leaving the pile. */
456 pile_info_p->size = pile_size(pile_info_p->size, YES);
458 /* mark as not being an actual character, just motion */
459 pile_info_p->ch = '\0';
464 /* handle newline -- go to next line of pile */
465 if (text[Pile_info.curr_offset] == '\n') {
466 /* prepare for next line, if any */
467 Pile_info.curr_line_p = Pile_info.curr_line_p->next;
468 Pile_info.curr_offset = -1;
470 /* Adjust vertical and horizontal to start next line of pile.
471 * For horizontal, go back to beginning of pile,
472 * then add the difference between this line's
473 * left of align and the pile's. */
474 if (Pile_info.curr_line_p != (struct PILED_LINE *) 0) {
475 pile_info_p->hor = - Pile_info.curr_x;
476 Pile_info.curr_x = Pile_info.width_left_of_align
477 - Pile_info.curr_line_p->width_left_of_align;
478 pile_info_p->hor += Pile_info.curr_x;
480 /* vertical is adjusted by height of font, less
481 * a little to jam together to look better. */
482 pile_info_p->vert = - fontheight(pile_info_p->font,
484 + POINT * pile_info_p->size / DFLT_SIZE;
485 Pile_info.curr_y += pile_info_p->vert;
488 pile_info_p->vert = pile_info_p->hor = 0.0;
492 pile_info_p->ch = '\0';
497 pile_info_p->hor = pile_info_p->vert = 0.0;
499 switch (text[Pile_info.curr_offset] & 0xff) {
503 mfont = str2mfont( text[Pile_info.curr_offset] & 0xff);
504 pile_info_p->font = mfont;
505 pile_info_p->size = text[Pile_info.curr_offset+1];
506 pile_info_p->ch = text[Pile_info.curr_offset+2];
507 Pile_info.curr_x += width(mfont,
508 pile_info_p->size, pile_info_p->ch);
509 Pile_info.curr_offset += 2;
513 (Pile_info.curr_offset)++;
514 pile_info_p->font = text[Pile_info.curr_offset];
515 pile_info_p->ch = '\0';
519 (Pile_info.curr_offset)++;
520 pile_info_p->size = text[Pile_info.curr_offset];
521 pile_info_p->ch = '\0';
525 pile_info_p->ch = (char) STR_SLASH;
530 pile_info_p->ch = '\0';
534 (Pile_info.curr_offset)++;
535 pile_info_p->vert = vert_distance(
536 DECODE_VERT(text[Pile_info.curr_offset]),
537 pile_info_p->font, pile_info_p->size);
538 pile_info_p->ch = '\0';
542 (Pile_info.curr_offset)++;
543 Backspace_dist = text[Pile_info.curr_offset];
544 backdist = backsp_width(pile_info_p->size);
545 pile_info_p->hor -= backdist;
546 Pile_info.curr_x -= backdist;
547 pile_info_p->ch = '\0';
552 /* These aren't really allowed in piles,
553 * but we can hit this code under some error cases,
554 * so we recognize them and do something reasonable.
556 (Pile_info.curr_offset)++;
557 pile_info_p->ch = text[Pile_info.curr_offset];
561 if ( IS_STR_COMMAND(text[Pile_info.curr_offset]) & 0xff) {
562 pfatal("unexpected STR_ value 0x%x in get_piled()",
563 text[Pile_info.curr_offset] & 0xff);
566 /* ordinary character */
567 Pile_info.curr_x += width(pile_info_p->font,
569 text[Pile_info.curr_offset]);
570 pile_info_p->ch = text[Pile_info.curr_offset];
579 /* given a pointer into a string, return the code for the next
580 * character in the string. This will be either the ASCII code
581 * or the music symbol code. This function will also update the
582 * pointer into the string and the font and size if appropriate.
583 * If there happens to be a music character in the middle of a string,
584 * the font (and maybe size) will temporarily be changed for the music
585 * font. This function keeps track of that.
587 * Restrictions: This function must be used by calling it on the beginning
588 * of a string, then to walk through that string. You don't have to go all the
589 * way to the end of a given string, but once you switch to a new string
590 * (from which you must begin at its beginning) you cannot go back to the
591 * middle of some other string that you had been working on before.
593 * This function calls the more general function which can also update
594 * vertical motion and alignment information. It is retained for backward
595 * compatibility for the places that don't care about those things.
599 next_str_char(str_p_p, font_p, size_p)
601 char **str_p_p; /* address of pointer into string. Will be incremented
602 * to beyond current character on return (this might
603 * be several bytes into the string if current char
604 * is a special string command */
605 int *font_p; /* pointer to font. Will be updated to new font if
607 int *size_p; /* pointer to size. Will be updated to new size if
611 /* place to hold things we don't care about from general function */
612 double dummy_vertical, dummy_horizontal;
616 return(nxt_str_char(str_p_p, font_p, size_p, &dummy_textfont,
617 &dummy_vertical, &dummy_horizontal, &dummy_in_pile, NO));
621 /* returns the next character in string, along with any font/size changes
622 * and horizontal/vertical motions that should happen before that character.
626 nxt_str_char(str_p_p, font_p, size_p, textfont_p, vertical_p, horizontal_p, in_pile_p, slash)
631 int *textfont_p; /* font disregarding music characters */
632 double *vertical_p; /* if non-zero, do vertical motion */
633 double *horizontal_p; /* if non-zero, do horizontal motion */
634 int *in_pile_p; /* will be set to YES if inside a pile */
635 int slash; /* if YES, return any STR_SLASH's found,
636 * else ignore them */
639 static char *prev_str_p; /* most recent value of *str_p_p */
640 static int textfont; /* current font, ignoring music chars */
641 static int textsize; /* current size, ignoring music chars */
642 static int pile_mode; /* if inside a pile */
644 struct PILE_INFO pile_info;
646 /* initialize to assume no vertical motion or alignment at this char */
647 *horizontal_p = *vertical_p = 0.0;
649 if (*str_p_p == (char *) 0) {
655 if (prev_str_p != *str_p_p) {
656 /* must be starting on a different string. Our
657 * remembered text font/size are no longer valid */
659 *textfont_p = textfont;
664 if (pile_mode == YES) {
665 /* call the special function for piled to get next */
666 pile_info.font = textfont;
667 pile_info.size = textsize;
668 pile_mode = get_piled(&pile_info);
670 /* save what it returned */
671 *font_p = pile_info.font;
672 *size_p = pile_info.size;
673 if (pile_info.font != FONT_MUSIC) {
674 textfont = pile_info.font;
675 *textfont_p = textfont;
676 textsize = pile_info.size;
679 /* Add motion to any accumulated so far */
680 *vertical_p += pile_info.vert;
681 *horizontal_p += pile_info.hor;
683 if (pile_info.ch == '\0') {
684 /* only motion this time, keep going */
687 else if ( (pile_info.ch & 0xff) == STR_SLASH &&
689 /* caller doesn't care about slashes */
693 *in_pile_p = pile_mode;
694 return (pile_info.ch);
699 if (**str_p_p == '\0') {
706 if ( ! IS_STR_COMMAND(**str_p_p)) {
707 /* just an ordinary character. Return it after updating
708 * pointer to point past it */
709 code = **str_p_p & 0xff;
711 prev_str_p = *str_p_p;
713 /* if last chacter was music character, put back real * font and size */
714 if (IS_MUSIC_FONT(*font_p)) {
721 /* special command. see which one */
722 switch ( (unsigned) (**str_p_p & 0xff) ) {
725 /* change font to specified font and point past it */
726 *font_p = *++(*str_p_p);
729 *textfont_p = textfont;
733 /* change size to specified size and point past it */
734 *size_p = *++(*str_p_p);
740 /* Page number and number of pages are strange cases.
741 * We don't necessarily know at this point
742 * how many digits long they will be. So we punt:
743 * just return % or #. For height, ascent, and descent,
744 * the % or # will be the same as any number
745 * of digits (or at least close enough, we hope).
746 * That means the only problem is when we are getting
747 * the width of a string or actually printing
748 * it, so we'll deal with those as special cases */
750 prev_str_p = *str_p_p;
754 prev_str_p = *str_p_p;
760 /* music character */
761 *font_p = str2mfont( (unsigned) (**str_p_p & 0xff) );
762 *size_p = *++(*str_p_p);
763 code = (*++(*str_p_p)) & 0xff;
765 prev_str_p = *str_p_p;
769 Backspace_dist = (*++(*str_p_p)) & 0xff;
771 prev_str_p = *str_p_p;
790 *vertical_p += vert_distance(
791 DECODE_VERT( *++(*str_p_p) ),
802 pile_info.font = textfont;
803 pile_info.size = textsize;
804 *str_p_p = prep_pile(*str_p_p, &pile_info);
805 *vertical_p += pile_info.vert;
806 *horizontal_p += pile_info.hor;
807 *size_p = pile_info.size;
814 prev_str_p = *str_p_p;
821 pfatal("bad internal format for string (%x)",
826 prev_str_p = *str_p_p;
831 /* Given an argument to STR_VERTICAL and a font and size,
832 * return the distance in inches to move vertically. */
835 vert_distance(vert_arg, font, size)
844 /* first convert to ratio relative to font height */
846 ratio = (double) vert_arg / (double) MAXVERTICAL;
849 ratio = (double) vert_arg / (double) (-MINVERTICAL);
851 return(ratio * fontheight(font, size));
855 /* return width of most recent backspace returned by next_str_char() */
860 int size; /* adjust to this point size */
863 return ( (double) Backspace_dist * ((double) size / (double) DFLT_SIZE)
868 /* Return width of current pile */
874 return(Pile_info.width_left_of_align + Pile_info.width_right_of_align);
878 /* adjust size when entering/leaving a pile. Called with current size and
879 * whether we are entering or leaving. It returns the new size. */
882 pile_size(size, in_pile)
884 int size; /* current size */
885 int in_pile; /* if YES, we are currently in a pile and are about to leave
886 * it; if NO, we are about to enter the pile */
892 /* entering pile. Save current size, and return new size */
893 Pile_info.orig_size = size;
894 /* constrain new size to be at least MINSIZE */
895 if ((Pile_info.new_size = PILE_A * size / PILE_B) < MINSIZE) {
896 Pile_info.new_size = MINSIZE;
898 return(Pile_info.new_size);
902 /* Restore size. If we're still at the size at the beginning
903 * of the pile, use the orginal value before the pile to avoid
904 * any possible roundoff errors going back and forth,
905 * otherwise adjust by inverse of the factor used when
906 * entering the pile. */
907 if (size == Pile_info.new_size) {
908 return(Pile_info.orig_size);
911 if ((new_size = (size * PILE_B) / PILE_A) > MAXSIZE) {
912 /* constrain to no larger than MAXSIZE */
921 /* given where a pile begins and the font/size at that point, return the
922 * width from the beginning to the pile to the alignment point of the pile. */
925 align_distance(string, font, size)
927 char *string; /* pile starts here */
932 struct PILE_INFO pile_info;
934 pile_info.font = font;
935 pile_info.size = size;
936 (void) prep_pile(string, &pile_info);
937 return(Pile_info.width_left_of_align);