/************************************************* * The PMW Music Typesetter - 3rd incarnation * *************************************************/ /* Copyright (c) Philip Hazel, 1991 - 2020 */ /* Written by Philip Hazel, starting November 1991 */ /* This file last modified: November 2020 */ /* This file contains code for outputting text items */ #include "pmwhdr.h" #include "pagehdr.h" #include "outhdr.h" /************************************************* * Output vocal underlay extension * *************************************************/ /* If the font has no underline, do nothing. There's also a global flag that suppresses extenders. Arguments: x0 x coordinate of start of extension x1 x coordinate of end of extension y y coordinate of the extension fontsize the size of font Returns: nothing */ void out_extension(int x0, int x1, int y, int fontsize) { int i; int uwidth = font_stringwidth(US"_", font_rm, fontsize); int length = x1 - x0; int count, remain; uschar s[256]; if (!curmovt->underlayextenders || uwidth <= 0) return; count = length/uwidth; remain = length - count*uwidth; if (count <= 0) { if (length > (uwidth*3)/4) { count = 1; remain = 0; } else return; } y = out_ystave - ((y + curmovt->extenderlevel) * main_stavemagn)/1000; x0 += main_stavemagn; for (i = 0; i < count; i++) s[i] = '_'; s[count] = 0; ps_string(s, font_rm, fontsize, &x0, &y, FALSE); /* Deal with a final part-line */ if (remain >= uwidth/5) { x1 -= uwidth; ps_string(US"_", font_rm, fontsize, &x1, &y, FALSE); } } /************************************************* * Output vocal underlay hyphens * *************************************************/ /* This is very heuristic. If the width is very small, output nothing. If less than the threshold, output a single, centred hyphen. Otherwise output hyphens spaced at 1/3 of the threshold, centred in the space. Take care not to have spaces that are too small at the ends. Ensure that a continuation hyphen at the start of a line is always printed, moving it left if necessary. Arguments: x0 x coordinate of start of extension x1 x coordinate of end of extension y y coordinate of the extension fontsize the size of font contflag TRUE if continuing from the previous line Returns: nothing */ void out_hyphens(int x0, int x1, int y, int fontsize, BOOL contflag) { uschar *hyphen = curmovt->hyphenstring; int unit = curmovt->hyphenthreshold/3; int hwidth = string_width(hyphen, font_rm, fontsize); int width = x1 - x0; int minwidth = 800 + (hwidth - font_stringwidth(US"-", font_rm, fontsize)); DEBUG(("out_hyphens() start\n")); if (contflag) minwidth += 3200; if (width < minwidth) { if (!contflag) return; width = minwidth; x0 = x1 - width; } y = out_ystave - (y * main_stavemagn)/1000; /* Deal with the case when the width is less than the threshold */ if (width < curmovt->hyphenthreshold) { if (contflag) out_string(hyphen, font_rm, fontsize, x0, y, 0); if (!contflag || width > unit) { out_string(hyphen, font_rm, fontsize, (x0 + x1 - hwidth)/2, y, 0); } } /* Deal with widths greater than the threshold */ else { int i; int count = width/unit; /* count is the number of gaps */ if (width - count*unit < unit/2) count -= 2; else count--; if (width - count*unit > unit) unit += unit/(3*count); if (contflag) { if (width - count*unit > (3*unit)/2) count++; } else x0 += (width - count*unit - hwidth)/2; /* We have special code that generates a "widthshow" command to do the whole thing with one string. However, we can do this only if the hyphen string contains no escapes. One day, extend this to expand the escapes; for the moment, just do it if the hyphen string is a single minus. */ if (Ustrcmp(hyphen, "-") == 0) { int hylen = Ustrlen(hyphen); int swidth = font_stringwidth(US" ", font_rm, fontsize); uschar s[256]; uschar *pp = s; while (count-- >= 0) { Ustrcpy(pp, hyphen); pp += hylen; *pp++ = ' '; } *pp = 0; ps_wtext(s, font_rm, fontsize, x0, y, unit - hwidth - swidth); } /* Otherwise we have to output each hyphen individually */ else for (i = 0; i <= count; i++) { out_string(hyphen, font_rm, fontsize, x0, y, 0); x0 += unit; } } DEBUG(("out_hyphens() end\n")); } /************************************************* * Output repeating string * *************************************************/ /* This function fills up the space with repeat copies of the string from the htypestr. These are customized hyphen fillers, specified as extra strings for "underlay" or "overlay" that is in reality something else, such as a 8va marking. Arguments: x0 x coordinate of start of extension x1 x coordinate of end of extension y y coordinate of the extension contflag TRUE if continuing from the previous line eolflag TRUE if continuing on to the next line htype the number of the htypestr that has the relevant strings Returns: nothing */ void out_repeatstring(int x0, int x1, int y, BOOL contflag, BOOL eolflag, int htype) { htypestr *h = main_htypes; int width = x1 - x0; int count, scount, slen, swidth; int unscaled_fontsize, fontsize; uschar *s; uschar buff[256]; DEBUG(("out_repeatstring() start\n")); while (--htype > 0 && h != NULL) h = h->next; if (h == NULL) error_moan(ERR91, out_bar, out_stave); /* Hard error */ unscaled_fontsize = ((curmovt->fontsizes)->fontsize_text)[h->size1]; fontsize = mac_muldiv(main_stavemagn, unscaled_fontsize, 1000); s = h->string1; slen = Ustrlen(s); swidth = string_width(s, h->font, fontsize); /* Deal with special string at continuation line start */ if (contflag && h->string2 != NULL) { int unscaled_fontsize2 = ((curmovt->fontsizes)->fontsize_text)[h->size2]; int fontsize2 = mac_muldiv(main_stavemagn, unscaled_fontsize2, 1000); int xw = string_width(h->string2, h->font, fontsize2); out_string(h->string2, h->font, fontsize2, x0, out_ystave - (y * main_stavemagn)/1000, 0); width -= xw; x0 += xw; } /* Allow for special terminating string (but not at eol) */ if (!eolflag && h->string3 != NULL) width -= string_width(h->string3, h->font, fontsize); count = width/swidth; scount = 255/slen; y = out_ystave - ((y + h->adjust) * main_stavemagn)/1000; while (count > 0) { uschar *p = buff; int nx0 = x0; int i; for (i = 0; i < scount; i++) { Ustrcpy(p, s); p += slen; nx0 += swidth; if (--count <= 0) break; } out_string(buff, h->font, fontsize, x0, y, 0); x0 = nx0; } if (!eolflag && h->string3 != NULL) out_string(h->string3, h->font, fontsize, x0, y, 0); DEBUG(("out_repeatstring() end\n")); } /************************************************* * Handle a text item * *************************************************/ /* This procedure is called after the note has been set up for printing, so that the max && min pitches etc. are known. It must be called before out_moff is reset! When text is at the end of a bar, "atbar" is true. Arguments: p the text item atbar TRUE if at end of a bar Returns: nothing */ void out_text(b_textstr *p, BOOL atbar) { uschar ss[256]; uschar *s = p->string; int flags = p->flags; BOOL above = (flags & text_above) != 0; BOOL endalign = (flags & text_endalign) != 0; BOOL postscript = (flags & text_ps) != 0; BOOL rehearse = (flags & text_rehearse) != 0; int stringwidth; int six = 6*main_stavemagn; int x = n_x - out_Xadjustment; int y = above? 20000 : -10000; int unscaled_fontsize = ((curmovt->fontsizes)->fontsize_text)[p->size]; int fontsize = mac_muldiv(main_stavemagn, unscaled_fontsize, 1000); int *matrix = rehearse? (curmovt->fontsizes)->fontmatrix_rehearse : ((curmovt->fontsizes)->fontmatrix_text)[p->size]; DEBUG(("out_text()%s: %s\n", ((flags & text_ul) != 0)? " underlay" :"", s)); /* If font is transformed, set the matrix, and then find the complete string width before any rotation (used for centring and right-aligning). */ if (matrix != NULL) memcpy(font_transform, matrix, 4*sizeof(int)); stringwidth = string_width(s, p->font, fontsize); /* Handle special positioning and rotation. If the "halfway" value is set, adjust the x coordinate if there is a note that follows in the bar. Otherwise, if a note-related offset is set, adjust the x coordinate by looking for the relevant musical offset. */ if (out_textX != NULL) { if (out_textX->halfway != 0) { if (out_moff < out_poslast->moff) x += (endalign? 0 : ((flags & text_centre) != 0)? six/2 : six) + mac_muldiv(out_barx + out_findXoffset(out_moff + n_length) - n_x - six, out_textX->halfway, 1000); } /* The offset value units are crotchets. */ else if (out_textX->offset != 0) { int offset = mac_muldiv(len_crotchet, out_textX->offset, 1000); x = out_barx + out_findAoffset(out_moff + offset); } /* No rotation for underlay or overlay */ if ((flags & text_ul) == 0 && out_textX->rotate != 0) font_rotate(out_textX->rotate); } /* ======================= Underlay and Overlay ======================= */ /* {Und,Ov}erlay text is centred. If it ends in '=' the position is remembered for starting the extender; if it ends in '-' the position is remembered for starting the row of hyphens. Note that an underlay string is represented as a pointer and a count, not as a C string, because the pointer may point into the longer, original input string. If an underlay string consists solely of '=' and an extender is outstanding, an extender is drawn to the current position and the start extender position is updated. If we are at the start of a line (indicated by a zero xstart value), draw the line at the current level, not the saved one. Note that # characters in the underlay string are converted to spaces. */ if ((flags & text_ul) != 0) { int i, j; uschar *pp, *qq, *cc; ulaystr **uu = &bar_cont->ulay; ulaystr *u = *uu; /* Find pending extension or hyphen data */ while (u != NULL && u->level != p->ulevel) { uu = &u->next; u = *uu; } /* Compute vertical level for this syllable */ y = ((flags & text_above) == 0)? out_sysblock->ulevel[out_stave] - p->ulevel * curmovt->underlaydepth : out_sysblock->olevel[out_stave] + (p->ulevel - olay_offset) * curmovt->overlaydepth; /* Deal with underlay extension line. There should always be an extension block, but we check, just in case. */ if (s[0] == '=' && p->ulen == 1) { if (u != NULL && u->type == '=') { int xx, yy; if (u->x == 0) { xx = out_sysblock->firstnoteposition + out_sysblock->xjustify - 4000; yy = y; } else { xx = u->x; yy = u->y; } out_extension(xx, x + 5*main_stavemagn, yy, fontsize); if (x > u->x) u->x = x; /* Set up for next part */ u->y = yy; } return; } /* Not an extension line - there is text to be output. Copy it into a working string, turning # characters into spaces, and stopping at the character '^', which indicates the end of the text to be centred, or the start of it, if there are two '^' characters in the string. In the latter case, find the length of the left hang, set the start of the centering bit, and read to the next '^'. However, don't mess with characters that are part of an escape sequence. */ pp = s; cc = qq = ss; i = 0; for (j = 0; j < 2; j++) { int k; /* Search for next '^' in string */ for (; i < p->ulen && *pp != '^'; i++) { if (*pp == '\\') { int dummy1, dummy2, len; uschar *rr = pp; uschar dummy3[80]; pp = string_escape(pp+1, dummy3, &dummy1, &dummy2); len = pp - rr; Ustrncpy(qq, rr, len); qq += len; i += len - 1; } else if (*pp == '#') { *qq++ = ' '; pp++; } else *qq++ = *pp++; } *qq = 0; /* If hit end of string or the second '^', break */ if (i >= p->ulen || j == 1) break; /* See if there's another '^' in the string; if not, break */ for (k = i+1; k < p->ulen; k++) if (s[k] == '^') break; if (k >= p->ulen) break; /* Left shift by the left hand width, adjust the start of the centred string, and continue on to the second '^'. */ x -= string_width(ss, p->font, fontsize); cc = qq; pp++; i++; } /* There are two underlay styles. In style 0, all syllables are centred, with ^ indicating the end of the centred text. In style 1, syllables that extend over more than one note are left-justified, unless they contain ^, which indicates centring. */ if (curmovt->underlaystyle == 0 || (*pp != '=' && (*pp != '-' || pp[1] != '='))) { int xorig = x; x -= string_width(cc, p->font, fontsize)/2; /* We have calculated a centring position for the underlay string, based on the normal position, which is the left-hand edge of the note. If in fact there has been no change to the position, (i.e. the centred part of the string has zero width), leave the position as it is, for left-alignment. If there has been a change, however, we must add 3 points to make the centring relative to the middle of the notehead. However, if this is a grace note in default format, always left-align. */ if (x != xorig && (n_length != 0 || curmovt->gracestyle != 0)) x += 3*main_stavemagn; /* Copy the rest of the string if stopped at '^'; else leave pp pointing at the following character. */ if (*pp == '^') { pp++; for (i++; i < p->ulen; i++) { if (*pp == '\\') { int dummy1, dummy2, len; uschar *rr = pp; uschar dummy3[80]; pp = string_escape(pp+1, dummy3, &dummy1, &dummy2); len = pp - rr; Ustrncpy(qq, rr, len); qq += len; i += len - 1; } else if (*pp == '#') { *qq++ = ' '; pp++; } else *qq++ = *pp++; } *qq = 0; } } /* Deal with printing a row of hyphens up to this syllable. If continuing at the start of a new line, use the current level rather than the saved level. Remember to take note of any leading spaces at the start of the current text. */ if (u != NULL && u->type == '-') { BOOL contflag; int x1 = x; int xx, yy; if (ss[0] == ' ') { int k; uschar spaces[80]; for (k = 0; ss[k] == ' '; k++) spaces[k] = ss[k]; spaces[k] = 0; x1 += font_stringwidth(spaces, p->font, fontsize); } if (u->x == 0) { xx = out_sysblock->firstnoteposition + out_sysblock->xjustify - 4000; yy = y + p-> y; contflag = TRUE; } else { xx = u->x; yy = u->y; contflag = FALSE; } if (u->htype == 0) out_hyphens(xx, x1 + p->x, yy, fontsize, contflag); else out_repeatstring(xx, x1 + p->x, yy, contflag, FALSE, u->htype); } /* Free up the hyphen or extender block. Extender blocks live till the next non "=" syllable, but are not drawn that far. */ if (u != NULL) { *uu = u->next; store_free(u); } /* Set up a new hyphen or extender block if required. We need to find the end of the current syllable, excluding any trailing spaces. There is no harm in just removing the trailing spaces now - if they are not printed, it won't be visible! */ if (*pp == '=' || *pp == '-') { u = store_Xget(sizeof(ulaystr)); u->next = bar_cont->ulay; bar_cont->ulay = u; while (qq > ss && qq[-1] == ' ') qq--; /* Find first trailing space */ *qq = 0; /* Terminate the string there */ /* Set up the data values */ u->x = x + p->x + string_width(ss, p->font, fontsize); u->y = y + p->y; u->type = *pp; u->level = p->ulevel; u->htype = p->htype; } /* The string to be printed has been built in ss */ s = ss; } /* ======================= General text ======================= */ /* Deal with not underlay. Adjust the x position if end or bar alignment is required, and adjust level according to the pitch of the just printed note/chord, if any, for the first text for any given note. Otherwise, the position is above or below the previous. Non-underlay text can be rotated; this was handled above, but we retained the string width before rotating the font so that centring and end-alignment are done first. */ else if ((flags & text_followon) == 0) { BOOL baralign = (flags & text_baralign) != 0; BOOL timealign = (flags & text_timealign) != 0; BOOL startbar = baralign || timealign; if (baralign) x = out_startlinebar? (out_sysblock->startxposition + out_sysblock->xjustify) : out_lastbarlinex; /* Time signature alignment. If not found, use the first musical event in the bar. */ else if (timealign) { if (out_startlinebar && mac_anystave(out_sysblock->showtimes)) x = out_sysblock->timexposition + out_sysblock->xjustify; /* In mid-line, or if no stave is printing a time signature, search for an appropriate position, defaulting to the first thing in the bar that can follow a time signature. There must be something! */ else { posstr *pt = out_postable; while (pt < out_poslast) /* Just for safety */ { if (pt->moff >= posx_timefirst) { x = out_barx + pt->xoff; break; } pt++; } } } /* Handle /e and /c. Note that stringwidth is the horizontal width, ignoring any font rotation. This means that rotation occurs after the text is positioned. */ if (endalign) x += ((atbar || startbar)? 1000 : six) - stringwidth; else if ((flags & text_centre) != 0) x += ((atbar || startbar)? 500 : six/2) - stringwidth/2; /* At the bar end, we adjust as for the last note or rest if end-aligned, because usually such text sticks back as far as the last note. Don't adjust for the next note when printing a rehearsal mark at the left of the stave. */ if (above) /* text above */ { if (out_textnextabove) y = out_textnextabove; else if ((!atbar || endalign) && (!rehearse || !curmovt->rehearsallsleft)) { int pt = misc_ybound(FALSE, n_nexttie, TRUE, TRUE); /* ties going out */ int ppt = misc_ybound(FALSE, n_prevtie, TRUE, TRUE); /* ties coming in */ if (ppt > pt) pt = ppt; if (pt + 2000 > y) y = pt + 2000; } /* Deal with "above at overlay level" */ if ((flags & text_atulevel) != 0) { y = out_sysblock->olevel[out_stave]; } /* Deal with text at absolute position above the stave */ else if ((flags & text_absolute) != 0) y = 16000; /* Save for repeated text */ if (!postscript) out_textnextabove = y + p->y + unscaled_fontsize; } else /* text below */ { if (out_textnextbelow) y = out_textnextbelow; else if (!atbar || endalign) { int pb = misc_ybound(TRUE, n_nexttie, TRUE, TRUE); int ppb = misc_ybound(TRUE, n_prevtie, TRUE, TRUE); if (ppb < pb) pb = ppb; if (pb - fontsize + 1000 < y) y = pb - fontsize + 1000; } /* Deal with "middle" text */ if ((flags & text_middle) != 0 && out_stave < out_laststave) { int my; int gap = out_sysblock->stavespacing[out_stave]; int st = out_stave; while (gap == 0 && ++st < out_laststave) if (mac_teststave(out_sysblock->notsuspend, st)) gap = out_sysblock->stavespacing[st]; my = - (gap/2 - 6000); if (my < y) y = my; } /* Deal with "below at underlay level" */ else if ((flags & text_atulevel) != 0) y = out_sysblock->ulevel[out_stave]; /* Deal with text at absolute position below the stave */ else if ((flags & text_absolute) != 0) y = 0; /* Save value for repeated text */ if (!postscript) out_textnextbelow = y + p->y - unscaled_fontsize; } } /* ======================= Generate follow-on text ======================= */ /* This outputs at the ending point of the previous text string, which has already been through the stave magnification, though any adjustment must be so scaled. */ else { out_string(s, p->font, fontsize, out_string_endx + p->x, out_string_endy - (p->y * main_stavemagn)/1000, 0); s = US""; /* Stops normal output */ } /* ======================= Generate output ======================= */ /* Parameters are now set up -- print, but omit null strings (to keep the PostScript smaller). */ if (*s != 0) { DEBUG(("string: %s\n", s)); /* Deal with rehearsal letters */ if (rehearse) { int style = curmovt->rehearsalstyle; int yextra = (style == text_box)? 2000 : (style == text_ring)? 4000 : 0; /* At the start of a bar, unless an offset is provided, if we are at the start of a line, align with the very start if rehearsallsleft is set or else with the first note. If not the start of a line, align with the previous bar line. Relative p->x is then added. */ if (out_moff == 0 && (out_textX == NULL || out_textX->offset == 0)) { if (out_startlinebar) { if (curmovt->rehearsallsleft) { x = out_sysblock->startxposition + out_sysblock->xjustify; switch (bar_cont->clef) { case clef_trebledescant: x += 15000; /* Fall through */ case clef_trebletenor: case clef_trebletenorB: case clef_treble: yextra += 3000; if (style == text_box) yextra += 1000; break; case clef_soprabass: x += 9000; yextra += 1000; break; default: break; } } else x = out_sysblock->firstnoteposition + out_sysblock->xjustify; } else x = out_lastbarlinex; } out_string(s, p->font, mac_muldiv(main_stavemagn, (curmovt->fontsizes)->fontsize_rehearse, 1000), x + p->x, out_ystave - ((y + yextra + p->y)*main_stavemagn)/1000, style); } /* Deal with included PostScript text */ else if (postscript) ps_pstext(s, x + p->x, out_ystave - (p->y*main_stavemagn)/1000); /* Deal with normal text */ else { if ((flags & (text_box | text_ring)) != 0) y += (above? 2 : (-2))*main_stavemagn; out_string(s, p->font, fontsize, x + p->x, out_ystave - ((y + p->y)*main_stavemagn)/1000, flags & (text_box | text_ring)); } } /* Reset font transformation */ font_reset(); DEBUG(("out_text() end\n", s)); } /* End of settext.c */