/************************************************* * The PMW Music Typesetter - 3rd incarnation * *************************************************/ /* Copyright (c) Philip Hazel, 1991 - 2020 */ /* Written by Philip Hazel, starting November 1991 */ /* This file last modified: June 2020 */ /* This file contains code for outputting a page */ #include "pmwhdr.h" #include "pagehdr.h" #include "outhdr.h" /************************************************* * Output one string * *************************************************/ /* We return the font which is current at the end of the string. If small caps are set, font_sc (which is larger than any font number) is added to the font. This is interpreted on input as well. The y values are absolute positions downwards from the top of the page. Arguments: s the string f the font in which to start pointsize the point size x x-coordinate for the start y y-coordinate for the start boxring flags: text_box or text-ring for boxed or ringed text, else 0 Returns: the font which is current at the end */ int out_string(uschar *s, int f, int pointsize, int x, int y, int boxring) { int i = 0; int xstart = x; int ystart = y; int orig_pointsize = pointsize; fontstr *fs; register int c; uschar ss[256]; if (f >= font_sc) { pointsize = (pointsize * curmovt->smallcapsize) / 1000; f -= font_sc; } fs = &(font_List[font_table[f]]); /* Process the characters in the string */ for (;;) { int nf, r; uschar xs[80]; GETCHARINC(c, s); if (c == 0) break; /* If the string buffer is getting full, output the string so far. This leaves plenty of room for escapes (which in practice are only a few bytes long). */ if (i > 250) { ss[i] = 0; ps_string(ss, f, pointsize, &x, &y, TRUE); i = 0; } /* Until we hit the escape character, '\', simply copy chars, except for quote and grave, which turn into closing and opening quotes, and for fi, which ligatures, for standardly encoded fonts. */ if (c != '\\') { uschar utf[8]; if (fs->stdencoding) { if (c == '`') c = QUOTE_LEFT; else if (c == '\'') c = QUOTE_RIGHT; else if (c == 'f' && *s == 'i' && !fs->fixedpitch && fs->hasfi) { c = CHAR_FI; s++; } } utf[misc_ord2utf8(c, utf)] = 0; Ustrcpy(ss + i, utf); i += Ustrlen(utf); continue; } /* Interpret the escape. It may return a string to print and/or a font change, and a flag to say whether the font change is permanent or not */ s = string_escape(s, xs, &nf, &r); /* If there is a font change, first output the string so far in the old font (if there is any string). */ if (nf >= 0) { if (i) { ss[i] = 0; ps_string(ss, f, pointsize, &x, &y, TRUE); i = 0; } /* If the new font is temporary, output the escaped string and mark it as empty. Otherwise, change the current font. */ if (r) { if (xs[0]) { ps_string(xs, nf, pointsize, &x, &y, TRUE); xs[0] = 0; } } /* If the new font is "small caps", leave the font alone, but adjust the size. Otherwise reset the original size. */ else { if (nf == font_sc) { if (pointsize == orig_pointsize) pointsize = (pointsize * curmovt->smallcapsize) / 1000; } else { f = nf; pointsize = orig_pointsize; fs = &(font_List[font_table[f]]); } } } /* Join the escape string onto the string so far */ Ustrcpy(ss+i, xs); i += Ustrlen(xs); } /* If there are any pending characters, output them. */ if (i) { ss[i] = 0; ps_string(ss, f, pointsize, &x, &y, TRUE); } /* Deal with boxed and/or ringed strings. Messy stuff added to cope with rotated text, leaving original code alone. */ if (boxring != 0) { int y0 = out_ystave - ystart - 2*main_stavemagn; int y1 = y0 + pointsize + main_stavemagn; /* Compute length of string along string. Putting the (int) cast next to the sqrt function gets a compiler warning. */ if (font_sinr != 0) { double xx = (double)x - (double)xstart; double yy = (double)y - (double)ystart; double zz = sqrt(xx*xx + yy*yy); x = xstart + (int)zz; } x += 2*main_stavemagn - pointsize/20; /* Boxed string - note the lines() routine is stave-relative. Draw 5 lines for the box to get the corner right at the starting position. */ if ((boxring & text_box) != 0) { int xx[6], yy[6]; xx[0] = xx[3] = xx[4] = xstart - 2000; xx[1] = xx[2] = xx[5] = x; yy[0] = yy[1] = yy[4] = yy[5] = y0; yy[2] = yy[3] = y1; /* If text is rotated, rotate about the initial point */ if (font_sinr != 0) { int j; for (j = 0; j <= 5; j++) { int xxx = xx[j] - xstart; int yyy = yy[j] - out_ystave + ystart; int xxxx = mac_muldiv(xxx, font_cosr, 1000) - mac_muldiv(yyy, font_sinr, 1000); int yyyy = mac_muldiv(yyy, font_cosr, 1000) + mac_muldiv(xxx, font_sinr, 1000); xx[j] = xxxx + xstart; yy[j] = yyyy + out_ystave - ystart; } } ps_lines(xx, yy, 6, pointsize/15); } /* Ringed string - the paths routine is also stave-relative */ if ((boxring & text_ring) != 0) { int xx[13], yy[13], cc[6]; int d = (2*pointsize)/7; int w = (2*(x - xstart + 2000))/7; cc[0] = path_move; cc[1] = cc[2] = cc[3] = cc[4] = path_curve; cc[5] = path_end; xx[0] = xx[9] = xx[12] = xstart - 2000; xx[3] = xx[6] = x; xx[1] = xx[8] = xx[0] + w; xx[2] = xx[7] = xx[3] - w; xx[4] = xx[5] = xx[3] + d; xx[10] = xx[11] = xx[0] - d; yy[0] = yy[3] = yy[12] = y1; yy[6] = yy[9] = y0; yy[1] = yy[2] = yy[0] + w; yy[4] = yy[11] = yy[0] - d; yy[5] = yy[10] = yy[6] + d; yy[7] = yy[8] = yy[6] - w; /* If text is rotated, rotate about the initial point */ if (font_sinr != 0) { int j; for (j = 0; j <= 12; j++) { int xxx = xx[j] - xstart; int yyy = yy[j] - out_ystave + ystart; int xxxx = mac_muldiv(xxx, font_cosr, 1000) - mac_muldiv(yyy, font_sinr, 1000); int yyyy = mac_muldiv(yyy, font_cosr, 1000) + mac_muldiv(xxx, font_sinr, 1000); xx[j] = xxxx + xstart; yy[j] = yyyy + out_ystave - ystart; } } ps_path(xx, yy, cc, pointsize/15); } } /* Return the current font, with font_sc added if we are in small caps. Remember the string's end point in global variables, for use with follow-on strings. */ out_string_endx = x; out_string_endy = y; return (pointsize == orig_pointsize)? f : f + font_sc; } /************************************************* * Output one head/foot line * *************************************************/ /* Called from out_heading() below. Argument: pointer to headstr Returns: nothing */ static void out_headfootline(headstr *p) { int f = p->font; int i; int *matrix = p->matrix; uschar *s = p->text; uschar left[READ_BUFFERSIZE]; /* No part can be longer than the maximum */ uschar centre[READ_BUFFERSIZE]; /* input buffer size as the original string */ uschar right[READ_BUFFERSIZE]; /* must be all on one line. */ if (matrix != NULL) memcpy(font_transform, matrix, 4*sizeof(int)); i = 0; while (*s != 0 && *s != '|') left[i++] = *s++; left[i] = 0; i = 0; if (*s == '|') s++; while (*s != 0 && *s != '|') centre[i++] = *s++; centre[i] = 0; i = 0; if (*s == '|') s++; while (*s != 0 && *s != '|') right[i++] = *s++; right[i] = 0; if (left[0]) { int w = string_width(left, f, p->size); font_xstretch = p->stretch; /* justification stretching */ f = out_string(left, f, p->size, 0, out_yposition, 0); font_xstretch = 0; if (-2000 < out_bbox[0]) out_bbox[0] = -2000; if (w + 2000 > out_bbox[2]) out_bbox[2] = w + 2000; } if (centre[0]) { int w = string_width(centre, f, p->size); int x = (curmovt->linelength - w)/2; f = out_string(centre, f, p->size, x, out_yposition, 0); if (x - 2000 < out_bbox[0]) out_bbox[0] = x - 2000; if (x + w + 2000 > out_bbox[2]) out_bbox[2] = x + w + 2000; } if (right[0]) { int w = string_width(right, f, p->size); f = out_string(right, f, p->size, curmovt->linelength - w, out_yposition, 0); if (curmovt->linelength - w - 2000 < out_bbox[0]) out_bbox[0] = curmovt->linelength - w - 2000; if (curmovt->linelength + 4000 > out_bbox[2]) out_bbox[2] = curmovt->linelength + 4000; } font_reset(); } /************************************************* * Output heading texts * *************************************************/ /* Called from out_page() below. Argument: pointer to chain of heading blocks Returns: nothing */ static void out_heading(headblock *h) { headstr *p = h->headings; DEBUG(("out_heading() start\n")); while (p != NULL) { /* Deal with normal heading/footing */ if (p->size > 0) { int descender = (4 * p->size)/10; out_yposition += p->spaceabove; if (out_yposition + descender > out_bbox[1]) out_bbox[1] = out_yposition + descender; if (out_yposition - p->size < out_bbox[3]) out_bbox[3] = out_yposition - p->size; out_headfootline(p); out_yposition += p->space; } /* Deal with a drawing. The drawing code is set up for use on staves; hence we must set up out_stave as well as the origin. We set out_stave negative to control error messages. */ else if (p->size < 0) { draw_ox = draw_oy = 0; out_stave = -1; out_ystave = out_yposition; out_dodraw(p->drawing, p->drawargs, FALSE); out_yposition += p->space; } /* Deal with PostScript heading/footing */ else ps_headfoot(p); /* Advance to next heading/footing */ p = p->next; } DEBUG(("out_heading() end\n")); } /************************************************* * Output a system * *************************************************/ /* Called from out_page() below. The system to be output is set in out_sysblock. Arguments: none Returns: nothing */ static void out_system(void) { snamestr **names = out_sysblock->stavenames; zcopystr *zcopy = curmovt->zcopy; int stave, lastystave; int leftbarx, rightbarx; DEBUG(("out_system() start\n")); out_zcopycount = 0; out_ystave = out_yposition; out_overdraw = NULL; /* Frequently used values */ out_bar = out_sysblock->barstart; out_laststave = curmovt->laststave; /* Make a copy of the continuation data - but see later for multiple stave zero copies. */ out_cont = misc_copycontstr(out_sysblock->cont, out_laststave, FALSE); /* Output the start-of-line matter on each stave, and at the same time compute the relative position of each stave. */ for (stave = 1; stave <= out_laststave; stave++) { DEBUG(("start of line matter for stave %d\n", stave)); out_depthvector[stave] = out_ystave - out_yposition; if (mac_teststave(out_sysblock->notsuspend, stave)) { stavestr *ss = curmovt->stavetable[stave]; snamestr *sname = (names==NULL)? NULL : names[stave]; /* Set current magnification */ mac_setstavesize(stave); /* Deal with stave name; there may be additional strings hung off the "extra" field. */ for (; sname != NULL; sname = sname->extra) { /* Deal with textual stave name */ if (sname->text != NULL) { BOOL vcentred = FALSE; int yoffset= -8*main_stavemagn; int size = ((curmovt->fontsizes)->fontsize_text)[sname->offset]; int linedepth = size; int *matrix = ((curmovt->fontsizes)->fontmatrix_text)[sname->offset]; uschar *t = sname->text; if (matrix != NULL) memcpy(font_transform, matrix, 4*sizeof(int)); /* Find the middle point of the line(s) of text - for vertically centred text there is a fudge to get it in the middle of a brace. */ if ((sname->flags & snf_vcentre) != 0) { int gap; int st; /* Scan from the current stave, looking for a stave with a non-zero stavespacing. That is, look for the distance down from this stave. We use this for additional vertical spacing only if neither it nor the stave that follows are suspended. */ for (st = stave; st < out_laststave; st++) { gap = out_sysblock->stavespacing[st]; if (gap != 0) { if (mac_teststave(out_sysblock->notsuspend, st)) vcentred = mac_teststave(out_sysblock->notsuspend, (st+1)); break; } } if (vcentred) yoffset += gap/2 - 2000; } /* Now adjust the offset according to the number of lines */ yoffset += (linedepth*4)/10 - ((sname->linecount - 1)*linedepth)/2; /* Now print the lines; if rotated, only one line, and we have to make further adjustments for the vertically centred case. */ if ((sname->flags & snf_vertical) != 0) { if (vcentred) yoffset += string_width(t, font_rm, size)/2 - 2000; font_rotate(90000); out_string(t, font_rm, size, out_sysblock->xjustify, out_ystave + yoffset, 0); } /* Horizontal labels */ else { int maxw = 0; uschar tt[256]; /* If both centre & right adjust flags are set, we need to find the length of the longest line of the text. Tedious, but there's no other way of doing it as far as I can see... */ if ((sname->flags & (snf_hcentre | snf_rightjust)) == (snf_hcentre | snf_rightjust)) { while (*t) { int w; int i = 0; while (*t != 0 && *t != '|') tt[i++] = *t++; tt[i] = 0; w = string_width(tt, font_rm, size); if (w > maxw) maxw = w; if (*t == '|') t++; } t = sname->text; /* Restore for printing */ } while (*t) { int i = 0; int adjust = 0; while (*t != 0 && *t != '|') tt[i++] = *t++; tt[i] = 0; if ((sname->flags & (snf_hcentre | snf_rightjust)) != 0) { int w = string_width(tt, font_rm, size); adjust = out_sysblock->startxposition - 6000 - w; if (curmovt->bracelist != NULL) adjust -= 6500; else if (curmovt->thinbracketlist != NULL) adjust -= 4000; if ((sname->flags & snf_hcentre) != 0) { if ((sname->flags & snf_rightjust) == 0) adjust /= 2; else adjust -= (maxw - w)/2; } } out_string(tt, font_rm, size, out_sysblock->xjustify + adjust, out_ystave + yoffset, 0); yoffset += linedepth; if (*t == '|') t++; } } font_reset(); } /* Deal with stave name drawing */ if (sname->drawing != NULL) { draw_ox = draw_oy = 0; out_stave = stave; out_dodraw(sname->drawing, sname->args, FALSE); } } /* No clefs, keys, etc. if the bar has no data, or if the stavelines value has the top bit set (for the old [percussion] action). */ if ((ss->barindex)[out_sysblock->barstart] != NULL) { if (ss->stavelines < 128) { out_writeclef(out_sysblock->startxposition + out_sysblock->xjustify + (curmovt->startline)->clefspace, out_ystave, out_cont[stave].clef, 10000, FALSE); if (misc_keywidth(out_cont[stave].key, out_cont[stave].clef) != 0) out_writekey(out_sysblock->keyxposition + out_sysblock->xjustify, out_ystave, out_cont[stave].clef, out_cont[stave].key); } if (mac_teststave(out_sysblock->showtimes, stave)) out_writetime(out_sysblock->timexposition + out_sysblock->xjustify, out_ystave, out_cont[stave].time); } /* Advance down to next stave */ out_ystave += out_sysblock->stavespacing[stave]; } } /* Compute the levels for copies of stave 0 that are to be printed. If two or more share a level (because of suspension), keep only the last (-1 => no print). Don't print one below the system depth. There will always be at least one block on the list. */ while (zcopy != NULL) { if (zcopy->stavenumber <= out_laststave) { zcopy->level = out_depthvector[zcopy->stavenumber]; if (zcopy->level > out_sysblock->systemdepth) zcopy->level = -1; else { zcopystr *zz = curmovt->zcopy; out_zcopycount++; while (zz != zcopy) { if (zz->level == zcopy->level) { zz->level = -1; out_zcopycount--; } zz = zz->next; } } } else zcopy->level = -1; zcopy = zcopy->next; } /* If we are outputting more than one copy of stave zero, we must set up private contstr pointers for each one. */ if (out_zcopycount > 1) { zcopy = curmovt->zcopy; while (zcopy != NULL) { if (zcopy->level >= 0) zcopy->cont = (struct contstr *) misc_copycontstr(out_sysblock->cont, 0, FALSE); zcopy = zcopy->next; } } /* Output the joining signs required at the left hand side of the system of staves, unless there is only one stave. */ if (out_sysblock->systemdepth > 0) { usint bracketed[STAVE_BITVEC_SIZE]; int bar = out_bar; DEBUG(("joining signs\n")); /* If there is an indent set, do true lefthand joins if required. Then adjust the bar number to point to the one where the rest of the joins will appear. */ if (out_sysblock->joinxposition != out_sysblock->startxposition) { if (curmovt->startjoin) { out_joinxposition = out_sysblock->startxposition + out_sysblock->xjustify; out_dojoinsign(curmovt->joinlist, NULL, join_barline, bar_single, bar, NULL); out_dojoinsign(curmovt->joindottedlist, NULL, join_barline, bar_dotted, bar, NULL); } bar += curmovt->startbracketbar; } /* Set x position for all remaining signs */ out_joinxposition = out_sysblock->joinxposition + out_sysblock->xjustify; /* Deal with solid and dotted lines */ out_dojoinsign(curmovt->joinlist, NULL, join_barline, bar_single, bar, NULL); out_dojoinsign(curmovt->joindottedlist, NULL, join_barline, bar_dotted, bar, NULL); /* Deal with (thick) brackets; bracketed gets set to the bracketed staves */ out_dojoinsign(curmovt->bracketlist, NULL, join_bracket, 0, bar, bracketed); /* Deal with thin brackets */ out_dojoinsign(curmovt->thinbracketlist, bracketed, join_thinbracket, 0, bar, NULL); /* Deal with braces */ out_dojoinsign(curmovt->bracelist, bracketed, join_brace, 0, bar, NULL); } /* Now go through the bars, outputting all the staves for each in turn. */ out_prevtieflag = 0; out_startlinebar = TRUE; out_notex = out_sysblock->startxposition; /* for slurs ending at bar start */ out_barx = out_sysblock->firstnoteposition + out_sysblock->xjustify; /* start of bar position */ out_lastbarlinex = out_barx; /* for continued nth time marks */ out_lastbarwide = FALSE; for (;;) { out_bar = out_setbar(); out_barx = out_lastbarlinex + out_sysblock->barlinewidth; if (out_bar > out_sysblock->barend) break; out_startlinebar = FALSE; out_notex = out_lastbarlinex; /* for slurs ending at bar start */ } /* Output a key or time change at line end if required, adjusting the position for a non-stretched barline. */ if ((out_sysblock->flags & sysblock_warn) != 0) { out_barx += curmovt->barlinespace - out_sysblock->barlinewidth; out_warnbar(); } /* Free the main cont data structure and any copies that have been set up for multiple stave zeros. */ misc_freecontstr(out_cont, out_laststave); if (out_zcopycount > 1) { zcopy = curmovt->zcopy; while (zcopy != NULL) { if (zcopy->level >= 0) misc_freecontstr((contstr *)zcopy->cont, 0); zcopy = zcopy->next; } } /* Now we know the final x position, we can output the staves. Nothing is output for stave 0, as it is always overprinted. Also, nothing is output for a stave with "omitempty" set, as it deals with its own stave lines. */ out_ystave = out_yposition; lastystave = -1; leftbarx = out_sysblock->startxposition + out_sysblock->xjustify; rightbarx = out_lastbarlinex; if (rightbarx > leftbarx) for (stave = 1; stave <= out_laststave; stave++) { DEBUG(("lines for stave %d\n", stave)); if (mac_teststave(out_sysblock->notsuspend, stave)) { if (out_ystave != lastystave) { stavestr *ss = curmovt->stavetable[stave]; if (!ss->omitempty && ss->stavelines > 0) { mac_setstavesize(stave); ps_stave(leftbarx, out_ystave, rightbarx, ss->stavelines & 127); lastystave = out_ystave; } } out_ystave += out_sysblock->stavespacing[stave]; } } /* If any drawing items have been saved up for execution after the stave lines have been drawn, do them now. */ while (out_overdraw != NULL) { overdrawstr *this = out_overdraw; out_overdraw = this->next; if (this->texttype) { memcpy(font_transform, this->d.t.matrix, 4*sizeof(int)); out_string(this->d.t.text, font_rm, this->d.t.fontsize, this->d.t.xx, this->d.t.yy, this->d.t.boxring); font_reset(); } else { int *v = &(this->d.g.data[0]); ps_setgray(this->d.g.gray); out_ystave = this->d.g.ystave; ps_path(v, v+this->d.g.count, v + 2*this->d.g.count, this->d.g.linewidth); store_free(this); ps_setgray(0); } } DEBUG(("out_system() end\n")); } /************************************************* * Top-level entry point for outputting a page * *************************************************/ /* The first sysblock of the page is set in out_sysblock, and the remainder are chained on. Arguments: none Return: nothing */ void out_page(void) { BOOL lastwasheading = FALSE; int topspace = curpage->topspace; DEBUG(("out_page() start\n")); /* Initialize bounding box - note in y-downwards coordinates */ out_bbox[0] = BIGNUMBER; out_bbox[2] = 0; out_bbox[1] = 0; out_bbox[3] = BIGNUMBER; /* Initialize for outputting the music */ ps_setgray(0); ps_setdash(0, 0, caj_butt); out_sysblock = curpage->sysblocks; out_yposition = out_drawstackptr = 0; font_reset(); /* Output any heading and any systems. Note that we must insert a stave's gap (plus one) between the last heading line and the first system (to account for the system depth). Note also that we insert the topspace *after* pageheadings, but *before* non-page headings. */ while (out_sysblock != NULL) { movtstr *oldmovt = curmovt; /* Call the output device (PostScript) function on change of movement; this allows, for example, a change of margin. */ curmovt = format_movt = out_sysblock->movt; if (curmovt != oldmovt) ps_newmovt(); /* Deal with a heading */ if (out_sysblock->type == sh_heading) { out_bar = 0; main_stavemagn = 1000; if (!((headblock *)out_sysblock)->pageheading) { out_yposition += topspace; topspace = 0; } out_heading((headblock *)out_sysblock); lastwasheading = TRUE; } /* Deal with a system */ else { if (lastwasheading) out_yposition += 17000; out_yposition += topspace; topspace = 0; if (out_yposition - 48000 < out_bbox[3]) out_bbox[3] = out_yposition - 48000; if (out_yposition + out_sysblock->systemdepth + 32000 > out_bbox[1]) out_bbox[1] = out_yposition + out_sysblock->systemdepth + 32000; out_system(); if (out_sysblock->xjustify - 10000 < out_bbox[0]) out_bbox[0] = out_sysblock->xjustify - 10000; if (out_lastbarlinex + 4000 > out_bbox[2]) out_bbox[2] = out_lastbarlinex + 4000; if ((out_sysblock->flags & sysblock_noadvance) == 0) out_yposition += out_sysblock->systemdepth + out_sysblock->systemgap; lastwasheading = FALSE; } out_sysblock = out_sysblock->next; } /* Deal with any footings */ if (curpage->footing != NULL) { out_bar = 0; main_stavemagn = 1000; out_yposition = main_pagelength + 20000000/main_magnification; out_heading(curpage->footing); } DEBUG(("out_page() end\n")); } /* End of out1.c */