/************************************************* * The PMW Music Typesetter - 3rd incarnation * *************************************************/ /* Copyright (c) Philip Hazel, 1991 - 2020 */ /* Written by Philip Hazel, starting November 1991 */ /* This file last modified: April 2020 */ /* This file contains code for miscellanous functions used for drawing slurs. */ #include "pmwhdr.h" #include "outhdr.h" #include "pagehdr.h" /************************************************* * Set up start of slur processing * *************************************************/ /* This is called when processing [slur], both while setting up the cont structure, and while actually outputting. Argument: the slur start item Returns: the slur processing structure that was set up */ slurstr * misc_setstartslur(bstr *p) { b_slurstr *pp = (b_slurstr *)p; slurstr *s = store_Xget(sizeof(slurstr)); s->slur = pp; s->maxy = -BIGNUMBER; s->miny = BIGNUMBER; s->gaps = NULL; s->section = 1; s->lastx = s->x = s->count = s->slopeleft = s->sloperight = 0; /* Put in a default y value in case the slur doesn't cross anything visible */ s->lasty = s->y = ((pp->flags & sflag_b) == 0)? 16000 : 0; /* If this was a crossing slur, we place it second on the stack, if possible. Otherwise put it at th top. */ if ((pp->flags & sflag_x) != 0 && bar_cont->slurs != NULL) { s->next = (bar_cont->slurs)->next; (bar_cont->slurs)->next = s; } else { s->next = bar_cont->slurs; bar_cont->slurs = s; } return s; } /************************************************* * Find and unchain given slur * *************************************************/ /* This is called when processing [endslur], both while setting up the cont structure, and while actually outputting. If [endslur] has an id, we seek that slur, yielding NULL if not found; otherwise the first on the chain is yielded. Argument: the endslur item Returns: the found slur structure, removed from the chain of active slurs */ slurstr * misc_getendslur(bstr *p) { slurstr *s = bar_cont->slurs; int slurid = ((b_endslurstr *)p)->id; if (slurid != 0) { slurstr **ss = &(bar_cont->slurs); BOOL found = FALSE; while (s != NULL) { if ((s->slur)->id == slurid) { found = TRUE; *ss = s->next; break; } ss = &(s->next); s = *ss; } if (!found) return NULL; } else bar_cont->slurs = s->next; return s; } /************************************************* * Find Bezier parameter fraction * *************************************************/ /* Given the coefficients of a Bezier curve, its x coordinate end points, and a fraction, compute the value of t (0 <= t <= 1) for which the x coordinate on the curve is the same as the x coordinate of the point that is the fraction of the x-distance between the end points. This is used when drawing partial curves. The code in this function had to be massaged carefully to ensure that exactly the same result is obtained when run native and under valgrind, in order to make the tests run clean. The differences were only in the 3rd decimal place, insignificant in the actual output, but of course the comparisons failed. Arguments: f the fraction a, b, c the Bezier coefficients x0, x1 the endpoint x coordinates Returns: the t value */ static double bezfraction(double f, double a, double b, double c, double x0, double x1) { double wanted = x0 + (x1 - x0)*f; /* The wanted x coordinate */ double start = 0.0; double stop = 1.0; double step = 0.1; /* Outer repeat loop */ for (;;) { double t; /* Inner loop covers the current range */ for (t = start; t < stop + step; t += step) { double x = ((a*t + b)*t + c)*t + x0; /* Make sure that -0.0 is actually 0.0; this can be different when running valgrind. */ if (fabs(x) < 0.000001) x = 0.0; /* If stepped past the wanted point, set new bounds and a smaller step unless we are close enough. */ if (x >= wanted) { if (fabs(x - wanted) < 10.0 || step < 0.001) return t; start = t - step; stop = t; step /= 10.0; break; } } /* If didn't reach it, return the right point */ if (t >= stop + step) return stop; } return f; /* Should never be obeyed */ } /************************************************* * Get coordinates of slur gap * *************************************************/ /* For a drawing function we need the coordinates of the end points and the midpoint of a gap in the normal coordinate system. This is very tedious to compute. The results are returned in a static vector. We have to repeat a lot of the work needed for actually drawing the relevant portion of the slur. Arguments: ix0, iy0 the start coordinates of the part-slur ix1, iy1 the end coordinates of the part slur flags slur flags co co parameter start t-value for start of gap (fixed point) stop t-value for end of gap (fixed point) Returns: vector of left, middle, right coordinates */ static int coords[6]; static int * getgapcoords(int ix0, int iy0, int ix1, int iy1, int flags, int co, int start, int stop) { int above, wiggly; int ox, oy; double xx, yy, w, v; double x0, x1, x2, x3, y0, y1, y2, y3; double a, b, c, f, g; double xxl, xxr, yyl, yyr, sa, ca; /* Compute values needed for curved slur drawing. */ above = ((flags & sflag_b) == 0)? (+1) : (-1); wiggly = ((flags & sflag_w) == 0)? (+1) : (-1); xx = (double)ix1 - (double)ix0; yy = (double)iy1 - (double)iy0; w = sqrt(xx*xx + yy*yy); sa = yy/w; /* sine */ ca = xx/w; /* cosine */ w /= 2.0; v = w*0.6667; if (v > 10000.0) v = 10000.0; co = (above * (co + ((xx > 20000)? 6000 : (int)(xx * 0.3))) * main_stavemagn)/1000; f = ((double)start)/1000.0; g = ((double)stop)/1000.0; /* Calculate the origin of the coordinate system where the slur would be drawn. We don't actually have to translate or rotate, since we are not actually going to draw anything here. */ ox = (ix0+ix1+6*main_stavemagn)/2; oy = (iy0+iy1)/2; /* Set up traditional Bezier coordinates for the complete slur. */ x0 = -w; x1 = v - w + (double)out_slurclx; x2 = w - v + (double)out_slurcrx; x3 = +w; y0 = 0.05; y1 = (double)(int)(co + out_slurcly); y2 = (double)(int)(co*wiggly + out_slurcry); y3 = 0.05; /* Calculate the coefficients for the original x parametric equation. */ a = x3 - x0 + 3.0*(x1 - x2); b = 3.0*(x2 - 2.0*x1 + x0); c = 3.0*(x1 - x0); /* The given fractions are fractions along the line joining the two end points. These do not correspond linearly with the parameter t of the complete curve, so we have to calculate new fractional values. */ if (f > 0.0 && f < 1.0) f = bezfraction(f, a, b, c, x0, x3); if (g > 0.0 && g < 1.0) g = bezfraction(g, a, b, c, x0, x3); /* Now calculate the new Bezier point coordinates for ends of the portion of the slur that we want. */ xxl = x0 + ((a*f + b)*f + c)*f; xxr = x0 + ((a*g + b)*g + c)*g; /* Now do exactly the same for the y points */ a = y3 - y0 + 3.0*(y1 - y2); b = 3.0*(y2 - 2.0*y1 + y0); c = 3.0*(y1 - y0); yyl = y0 + ((a*f + b)*f + c)*f; yyr = y0 + ((a*g + b)*g + c)*g; /* Now we have to get those coordinates back into the normal coordinate system. First rotate, then remove the effect of the translation. */ coords[0] = (int)(xxl * ca - yyl * sa) + ox; coords[1] = (int)(yyl * ca + xxl * sa) + oy; coords[4] = (int)(xxr * ca - yyr * sa) + ox; coords[5] = (int)(yyr * ca + xxr * sa) + oy; /* Set up the mid point values and return the vector. */ coords[2] = (coords[0] + coords[4])/2; coords[3] = (coords[1] + coords[5])/2; return coords; } /************************************************* * Output text in a slur or line gap * *************************************************/ /* Arguments: gt the gap text structure x, y coordinates of the middle of the gap num, den slope parameters for the gap Returns: nothing */ static void out_gaptext(gaptextstr *gt, int x, int y, int num, int den) { int *matrix; int width, sn, cs; int unscaled_fontsize = ((curmovt->fontsizes)->fontsize_text)[gt->size]; int fontsize = mac_muldiv(main_stavemagn, unscaled_fontsize, 1000); matrix = ((curmovt->fontsizes)->fontmatrix_text)[gt->size]; if (matrix != NULL) memcpy(font_transform, matrix, 4*sizeof(int)); if (num != 0) /* Not horizontal */ { double hy = sqrt((double)num*(double)num + (double)den*(double)den); font_rotate((int)((45000.0*atan2((double)num, (double)den))/atan(1.0))); sn = (int)((1000.0*(double)num)/hy); cs = (int)((1000.0*(double)den)/hy); } else { sn = 0; cs = 1000; den = 1; /* To stop division by 0 below */ } width = string_width(gt->text, font_rm, fontsize); x = x - width/2 + mac_muldiv(gt->x, cs, 1000) - mac_muldiv(gt->y - fontsize/4, sn, 1000); y = y - (width*num)/(2*den) + mac_muldiv(gt->x, sn, 1000) + mac_muldiv(gt->y - fontsize/4, cs, 1000); out_string(gt->text, font_rm, fontsize, x, out_ystave - y, gt->flags); font_reset(); } /************************************************* * Compute parameters & draw a slur or line * *************************************************/ /* This function is called when [endslur] is reached, or at the end of a line. Arguments: s the active slur structure x1 the x coordinate of the end of the slur npitch pitch of the final note eol TRUE if this slur goes to the end of the line Returns: nothing */ void misc_drawslur(slurstr *s, int x1, int npitch, BOOL eol) { b_slurstr *ss = s->slur; b_slurmodstr *sm = NULL; b_slurmodstr *smm = ss->mods; BOOL sol; BOOL laststemup = out_laststemup[out_stave]; int adjustco = 0; int slurflags = ss->flags; int lineslur = (slurflags & sflag_l) != 0; int below = (slurflags & sflag_b) != 0; int absolute = (slurflags & sflag_abs) != 0; int lay = (slurflags & sflag_lay) != 0; int use_endmoff = ((slurflags & sflag_cx) != 0)? out_moff : out_lastmoff; int line_rc_adjust = lineslur? 3000 : 0; int x0 = s->x; int y0 = s->y; int y1; /* For continued slurs in style 0 we have to adjust the apparent end points and then draw only part of the resulting slur. These are the start and end fractional values. */ int dstart = 0; int dstop = 1000; /* Note: for absolute and {und,ov}erlay positioning, all this computation is later over-ridden. We leave it in place so as not to disturb things for the x values. */ /* End of line slurs take their vertical end from the max/min under them. Allow for the fact that a line slur gets its end moved 3 pts "past" the note position. Also, suppress wiggles for the first section - the other parts do the suppression with the sol test and need to have the flag still set in order to flip the above/below bit. Note that the final note has not yet been incorporated into the max/min. */ if (eol) { if (below) y1 = (s->lasty < s->miny)? s->lasty : s->miny; else y1 = (s->lasty > s->maxy)? s->lasty : s->maxy; if (lineslur) x1 -= 3000; if (x0 != 0) slurflags &= ~sflag_w; /* No wiggle */ slurflags |= sflag_or; /* Open on right if line */ } else /* Not end of line slur */ { y1 = (npitch == 0)? L_2L : misc_ybound(below, n_prevtie, TRUE, TRUE); /* If the last note was beamed, and the slur is on the same side as the beam, we need to put in an additional bit of space for clearance. Also, if the slur is on the opposite side to the stem, ditto. */ if (below) { if (laststemup || out_lastnotebeamed) y1 -= 1000; } else { if (!laststemup || out_lastnotebeamed) y1 += 1000; } } /* Set up left position at line start; if x1 is also zero it means we have hit an [es] or [el] immediately after a bar line at a line break. This is not an error; we just invent a small distance. Turn off the wiggle flag, but if it was set, flip the above/below status. We must also do a vertical adjustment for the final part of a split wiggly slur. */ if (x0 == 0) { sol = TRUE; slurflags |= sflag_ol; /* Open on left if line */ if ((slurflags & sflag_w) != 0) { slurflags &= ~sflag_w; /* No wiggle */ below = !below; /* Other direction */ slurflags ^= sflag_b; /* Must do this too */ y1 = (npitch == 0)? L_2L : misc_ybound(below, n_prevtie, TRUE, TRUE); } x0 = out_sysblock->firstnoteposition + out_sysblock->xjustify - 10500; if (x1 == 0) { x1 = x0 + 10500; if (lineslur) x0 -= 10000; /* lines normally start 10 pts right */ } } else sol = FALSE; /* For wiggly slurs, move the final point to the other end of the last note. We don't attempt any other fancy adjustments for these slurs. */ if ((slurflags & sflag_w) != 0) { y1 = (npitch == 0)? L_2L : misc_ybound(!below, n_prevtie, TRUE, TRUE); if (!below && !laststemup && (n_flags & nf_stem) != 0) x1 -= 5*main_stavemagn; } /* For non-wiggly slurs, make adjustments according to the starting and ending slopes if there are more than three notes in the slur. The "starting" slope actually looks at more than the first note, but we aren't clever enough to do the same at the end. The curvature is adjusted according to the max/min pitch under the slur. */ else if (below) /* slur below */ { int miny = s->miny; if (!laststemup && /* Note has down stem */ (n_flags & nf_stem) != 0 && /* Note has a stem */ !lineslur && /* Not a line (i.e. a slur) */ !eol && /* Not an end of line slur */ !main_righttoleft) /* Not right-to-left */ x1 -= 5*main_stavemagn; if (s->count > 3) { if (s->slopeleft < -400) y0 -= (s->slopeleft < -600)? 4000: 2000; if (s->sloperight > 400) y1 -= (s->sloperight > 600)? 4000: 2000; } if (miny < y0 && miny < y1) { int adjust = (y0 + y1)/2 - miny; if (lineslur) { if ((slurflags & sflag_h) != 0) y0 = y1 = miny; else { y0 -= adjust; y1 -= adjust; } } else adjustco += adjust; } } else /* slur above */ { int maxy = s->maxy; if (s->count > 3) { if (s->slopeleft > 400) y0 += (s->slopeleft > 600)? 4000 : 2000; if (s->sloperight < -400) y1 += (s->sloperight < -600)? 4000 : 2000; } if (maxy > y0 && maxy > y1) { int adjust = maxy - (y0 + y1)/2; if (lineslur) { if ((slurflags & sflag_h) != 0) y0 = y1 = maxy; else { y0 += adjust; y1 += adjust; } } else adjustco += adjust; } } /* Deal with the horizontal option (horizontal line slurs handled above, but other cases not yet). */ if ((slurflags & sflag_h) != 0) { if ((!below && y1 > y0) || (below && y1 < y0)) y0 = y1; else y1 = y0; } /* If this is a curved slur, arrange that the end points are not on staff lines, and ensure that for longish slurs, the centre adjustment is at least 2. Also do this for steep slurs, but not if absolute or at underlay level or if the slur is short. */ if (!lineslur) { int ay0 = abs(y0); int ay1 = abs(y1); if (below) { if (y0 >= L_1S && (ay0 % 4000) < 500) y0 -= 1000; if (y1 >= L_1S && (ay1 % 4000) < 500) y1 -= 1000; } else { if (y0 <= L_5S && (ay0 % 4000) < 500) y0 += 1000; if (y1 <= L_5S && (ay1 % 4000) < 500) y1 += 1000; } if (x1 - x0 > 72000 && adjustco < 2000) adjustco = 2000; else if (!absolute && !lay && x1 - x0 > 24000) { int overall_slope = mac_muldiv(y1 - y0, 1000, x1 - x0); if (abs(overall_slope) > 500 && adjustco < 2000) adjustco = 2000; } } /* If this is a line-type "slur", ensure that the default pitches (i_e. before adding user movement) are above or below the staff, as necessary. */ else { if (below) { if (y0 > L_1L) y0 = L_1L; if (y1 > L_1L) y1 = L_1L; } else { if (y0 < L_6L) y0 = L_6L; if (y1 < L_6L) y1 = L_6L; } } /* If the slur or line is marked as "absolute", then the vertical positions are specified without reference to any intervening notes. We allow all the above to happen, so that the correct x values get computed, and also this feature was added later and it is easier not to disturb the above code. Ditto for slurs/lines that are drawn at the underlay or overlay level. */ if (absolute) { y0 = y1 = below? 0 : 16000; } else if (lay) { y0 = y1 = below? out_sysblock->ulevel[out_stave] : out_sysblock->olevel[out_stave]; } /* Finally, apply manual adjustments. All endpoints of all sections are affected by the "ally" value. */ y0 += ss->ally; y1 += ss->ally; /* Most slurs appear on at most two lines; we need the slurmod structure for sequence number zero for endpoint adjustments in all cases except for the middle sections of a slur that exists on more than 2 lines, so get it in all cases (if it exists). */ while (smm != NULL && smm->sequence != 0) smm = smm->next; /* If this is part of a split slur, we need the slurmod structure that matches this section, and in all cases its values are used. */ if (sol || eol) { sm = ss->mods; while (sm != NULL && sm->sequence != s->section) sm = sm->next; if (sm != NULL) { if (sm->lxoffset != 0) { int offset = mac_muldiv(len_crotchet, sm->lxoffset, 1000); x0 = out_barx + out_findAoffset(s->moff + offset); } /* When x1 default for a line is set by musical offset, lose the additional 3pts that are added to get past the final note in the default case. */ if (sm->rxoffset != 0) { int offset = mac_muldiv(len_crotchet, sm->rxoffset, 1000); x1 = out_barx + out_findAoffset(use_endmoff + offset) - line_rc_adjust; } x0 += sm->lx; x1 += sm->rx; y0 += sm->ly; y1 += sm->ry; adjustco += sm->c; out_slurclx = (sm->clx * main_stavemagn)/1000; out_slurcly = (sm->cly * main_stavemagn)/1000; out_slurcrx = (sm->crx * main_stavemagn)/1000; out_slurcry = (sm->cry * main_stavemagn)/1000; } } /* Other values depend on which bit of a split slur is being drawn. If it is neither the starting section nor the ending section (that is, it's a whole line of middle slur), use just the values from this section's data block. In other words, there's no more to do. */ if (sol && eol) { } /* The final portion of a split slur. Use values from its block, plus values from the zero block for the right-hand end, which is also applied to the vertical movement of the left-hand end. */ else if (sol) { if (smm != NULL) { if (smm->rxoffset != 0) { int offset = mac_muldiv(len_crotchet, smm->rxoffset, 1000); x1 = out_barx + out_findAoffset(use_endmoff + offset) - line_rc_adjust; } x1 += smm->rx; y1 += smm->ry; y0 += smm->ry; } } /* The first section of a split slur. Use values from its block plus values from the zero block for the left-hand end, which is also applied to the vertical movement of the right-hand end. */ else if (eol) { if (smm != NULL) { if (smm->lxoffset != 0) /* Relative to start of slur */ { int offset = mac_muldiv(len_crotchet, smm->lxoffset, 1000); x0 = out_barx + out_findAoffset(s->moff + offset); } x0 += smm->lx; y0 += smm->ly; y1 += smm->ly; } } /* An unsplit slur. Use values from the zero block. */ else if (smm != NULL) { if (smm->lxoffset != 0) /* Relative to start of slur */ { int offset = mac_muldiv(len_crotchet, smm->lxoffset, 1000); x0 = out_barx + out_findAoffset(s->moff + offset); } if (smm->rxoffset != 0) { int offset = mac_muldiv(len_crotchet, smm->rxoffset, 1000); x1 = out_barx + out_findAoffset(use_endmoff + offset) - line_rc_adjust; } x0 += smm->lx; x1 += smm->rx; y0 += smm->ly; y1 += smm->ry; adjustco += smm->c; out_slurclx = (smm->clx * main_stavemagn)/1000; out_slurcly = (smm->cly * main_stavemagn)/1000; out_slurcrx = (smm->crx * main_stavemagn)/1000; out_slurcry = (smm->cry * main_stavemagn)/1000; } /* Need to correct for the jog length for absolute line slurs, so that the line is at the height specified. Also need to move above slurs up. */ if (lineslur && (absolute || lay)) { int x = adjustco + 3000; if (!below) x = -x; y0 += x; y1 += x; } /* Adjust the verticals for the stave magnification */ y0 = mac_muldiv(y0, main_stavemagn, 1000); y1 = mac_muldiv(y1, main_stavemagn, 1000); /* Make adjustments and fudges for continued curved slurs if the style is not the default. */ if (eol && !lineslur && curmovt->endlineslurstyle != 0) { x1 += x1 - x0; y1 += (y1 - y0)/5; adjustco += 2*(y1 - y0)/3; dstop = 500; } if (sol && !lineslur && curmovt->endlineslurstyle != 0) { x0 -= x1 - x0; y0 -= (y1 - y0)/5; adjustco += 2*(y1 - y0)/3; dstart = 500; } /* If this is a line slur, there may be a chain of gaps to be dealt with. Note that the out_slur routine adds 7*main_stavemagn to the x1 value, to get it past the final note. */ if (lineslur && s->gaps != NULL) { b_linegapstr *lg; gapstr *pg = s->gaps; int fudge = 7*main_stavemagn; /* First scan through and compute positions for any that are specified as a fraction of the way along the line. */ while (pg != NULL) { lg = pg->gap; if (lg->hfraction >= 0) { pg->x = lg->xadjust + x0 + mac_muldiv(x1 + fudge - x0, lg->hfraction, 1000); } pg = pg->next; } /* The gaps may be in any order, horizontally. We could sort them before processing, but it is just as easy to pick out the leftmost and process it until there are none left. */ while (s->gaps != NULL) { gapstr **gg = &(s->gaps); /* Parent ptr */ gapstr *g = *gg; /* Active block ptr */ int firstx = g->x; int xg0, yg0, num, den, xwidth; /* Scan to find the leftmost */ pg = g; while (pg->next != NULL) { if ((pg->next)->x < firstx) { gg = &(pg->next); g = *gg; firstx = g->x; } pg = pg->next; } /* Draw the line to this gap, unless it has negative length. */ num = y1 - y0; den = x1 + fudge - x0; lg = g->gap; if (num == 0) /* Optimize out the common case (horizontal) */ { xwidth = lg->width; xg0 = g->x - xwidth/2; yg0 = y0; } else { double dnum = (double)num; double dden = (double)den; xwidth = (int)(((double)(lg->width) * dden) / sqrt(dnum*dnum + dden*dden)); xg0 = g->x - xwidth/2; yg0 = y0 + mac_muldiv(xg0 - x0, num, den); } if (x0 < xg0) out_slur(x0, y0, xg0 - fudge, yg0, slurflags | sflag_or, adjustco, 0, 1000); /* Update the starting position for the next section. */ x0 = xg0 + xwidth; y0 = yg0 + mac_muldiv(x0 - xg0, num, den); slurflags |= sflag_ol; /* If there is an associated draw function, set up the coordinates and call it. Note that lines are always drawn 3 points above or below the given y value, to leave space for the jog. */ if (lg->draw != NULL) { draw_lgx = xwidth / 2; draw_lgy = (y0 - yg0)/2; draw_ox = xg0 + draw_lgx; draw_oy = yg0 + draw_lgy + (below? (-3000) : (3000)); draw_gap = below? -1000 : +1000; out_dodraw(lg->draw, lg->args, FALSE); draw_lgx = draw_lgy = draw_gap = 0; } /* If there is an associated text string, arrange to print it centred in the gap. */ if (lg->gaptext != NULL) { out_gaptext(lg->gaptext, (xg0 + x0)/2, (y0 + yg0)/2 + (below? (-3000) : (3000)), num, den); } /* Extract from the chain and free the block */ *gg = g->next; store_free(g); } /* Now draw the rest of the line, if there is any left. */ if (x0 < x1) out_slur(x0, y0, x1, y1, slurflags, adjustco, 0, 1000); } /* The ability to have gaps in curved slurs was added at a later stage. Rather than mess with the above code, put in separate code to handle it. The data is the same as for line gaps. */ else if (s->gaps != NULL) { b_linegapstr *lg; gapstr *pg = s->gaps; int xlength = x1 + 7*main_stavemagn - x0; int start = dstart; int stop; /* First scan through and compute positions for any that are specified as a fraction of the way along the line. */ while (pg != NULL) { lg = pg->gap; if (lg->hfraction >= 0) { pg->x = x0 + mac_muldiv(xlength, lg->hfraction, 1000) + lg->xadjust; } pg = pg->next; } /* The gaps may be in any order, horizontally. We could sort them before processing, but it is just as easy to pick out the left- most and process it until there are none left. */ while (s->gaps != NULL) { gapstr **gg = &(s->gaps); /* Parent ptr */ gapstr *g = *gg; /* Active block ptr */ int firstx = g->x; /* Scan to find the leftmost */ pg = g; while (pg->next != NULL) { if ((pg->next)->x < firstx) { gg = &(pg->next); g = *gg; firstx = g->x; } pg = pg->next; } /* Draw the slur to this gap, unless it has negative length. */ lg = g->gap; stop = mac_muldiv(g->x - lg->width/2 - x0, 1000, xlength); if (stop > start) out_slur(x0, y0, x1, y1, slurflags, adjustco, start, stop); /* Compute start of next piece */ start = mac_muldiv(g->x + lg->width/2 - x0, 1000, xlength); /* If there is an associated draw function, set up the coordinates and call it. */ if (lg->draw != NULL) { int *c = getgapcoords(x0, y0, x1, y1, slurflags, adjustco, stop, start); draw_ox = c[2]; draw_oy = c[3]; draw_lgx = c[2] - c[0]; draw_lgy = c[3] - c[1]; draw_gap = below? -1000 : +1000; out_dodraw(lg->draw, lg->args, FALSE); draw_lgx = draw_lgy = draw_gap = 0; } /* If there's associated text, output it. */ if (lg->gaptext != NULL) { int *c = getgapcoords(x0, y0, x1, y1, slurflags, adjustco, stop, start); out_gaptext(lg->gaptext, c[2], c[3], c[5]-c[1], c[4]-c[0]); } /* Extract from the chain and free the block */ *gg = g->next; store_free(g); } /* Now draw the rest of the slur, if there is any left. */ if (start < 1000) out_slur(x0, y0, x1, y1, slurflags, adjustco, start, dstop); } /* Else we have a slur or line with no gaps specified. Output it, provided it has some positive horizontal length. If there isn't enough length, generate an error and invent some suitable length. */ else { if (x0 >= x1) { error_moan(ERR63, out_bar, out_stave); if (x0 == x1) x1 = x0 + 10000; else { int temp = x0; x0 = x1; x1 = temp; } } out_slur(x0, y0, x1, y1, slurflags, adjustco, dstart, dstop); } /* Reset the globals that hold control point adjustments. */ out_slurclx = out_slurcly = out_slurcrx = out_slurcry = 0; /* Free the slur block, which is now finished with. */ store_free(s); } /************************************************* * Draw slur or line from given values * *************************************************/ /* Originally there were only simple slurs, and these were drawn by the ps_slur() function. When things got more complicated, additional work would have had to be done in the PostScript header file. However, in the meanwhile, the ps_path() function had been invented for drawing arbitrary shapes at the logical (non-device) level. This function (out_slur()) is now called where ps_slur() used to be called. In principle, it could do all the output. However, to keep the size of PostScript down and for compatibility with the previous PostScript, it still calls ps_slur() for PostScript output of complete, non-dashed, curved slurs that can be handled by the old code. New functionality is added in here, and in time I may remove the special PostScript into here as well. Each change will cause the PostScript to change, and hence the tests to fail to validate... Note that as well as the parameters passed as arguments, there are also parameter values in the global variables out_slurclx, out_slurcly, out_slurcrx, and out_slurcry for corrections to the control points. The t-values are the Bezier parameter values for drawing part slurs, given as fixed point values between 0 and 1.0 respectively. For a whole slur, their int values are therefore 0 and 1000. Parts of the code in this function had to be massaged carefully to ensure that exactly the same result is obtained when run native and under valgrind, in order to make the tests run clean. The differences were only in the 3rd decimal place, insignificant in the actual output, but of course the comparisons failed. Arguments: ix0, iy1 coordinates of the start of the slur ix1, iy1 coordinates of the end of the slur flag the slur flags co the co ("centre out") value start the t-value at slur start (fixed point) ) for drawing stop the t-value at slur end (fixed point) ) part-slurs Returns: nothing */ void out_slur(int ix0, int iy0, int ix1, int iy1, int flags, int co, int start, int stop) { int x[10], y[10], cc[10]; int above, wiggly, ed_adjust; double zz[4]; double temp; double xx, yy, w, v; double x0, x1, x2, x3, y0, y1, y2, y3; double ax, ay, bx, by, cx, cy, f, g; if (ix1 == ix0 && iy1 == iy0) return; /* Avoid crash */ /* Use ps_slur() to output complete, curved, non-dashed slurs to maintain compatibility and smaller PostScript files. */ if (start == 0 && stop == 1000 && (flags & (sflag_l | sflag_i)) == 0) { ps_slur(ix0, iy0, ix1, iy1, flags, co); return; } /* Compute values needed by both lines and curved slurs */ xx = (double)ix1 - (double)ix0; yy = (double)iy1 - (double)iy0; above = ((flags & sflag_b) == 0)? (+1) : (-1); /* Handle straight-line "slurs". For these, the start and stop values are not used, as partial lines are drawn as separate whole lines with appropriate jog flags. */ if ((flags & sflag_l) != 0) { int lineflags = 0; int adjust = 0; int thickness = (3*main_stavemagn)/10; ix1 += 7*main_stavemagn; co = mac_muldiv((co + 3000)*above, main_stavemagn, 1000); /* Convert the flags to the tie flags used by the ps_line function, then output the main portion of the line. Set the savedash flag if drawing a dotted line so that the jogs are drawn with the same dash settings. */ if ((flags & sflag_i) != 0) lineflags |= ((flags & sflag_idot) == 0)? tief_dashed : tief_dotted | tief_savedash; if ((flags & sflag_e) != 0) lineflags |= tief_editorial; ps_line(ix0, iy0 + co, ix1, iy1 + co, thickness, lineflags); /* Don't pass any flag settings for drawing the jogs; for dotted lines the previous savedash ensures that the same setting is used for them. For dashed lines the jogs shouldn't be dashed. For dotted lines we may need to lengthen the jog to ensure at least one extra dot is drawn, and we change the thickness. Also, reduce the gap length slightly because there's an optical illusion that makes it look bigger than it is. Avoid redrawing the dot at the joining point. */ if ((flags & sflag_idot) != 0) { thickness = main_stavemagn; ps_setdash(out_dashlength, (out_dashgaplength*95)/100, caj_round); if (abs(co) < 2*out_dashlength + out_dashgaplength) adjust = above*(2*out_dashlength + out_dashgaplength) - co; } else co += (above*thickness)/2; if ((flags & sflag_ol) == 0) ps_line(ix0, iy0 + co - above*(out_dashlength+out_dashgaplength), ix0, iy0 - adjust, thickness, 0); if ((flags & sflag_or) == 0) ps_line(ix1, iy1 + co - above*(out_dashlength+out_dashgaplength), ix1, iy1 - adjust, thickness, 0); ps_setdash(0, 0, caj_butt); /* Clear saved setting if no jogs */ return; } /* Compute values needed for curved slur drawing. */ wiggly = ((flags & sflag_w) == 0)? (+1) : (-1); w = sqrt(xx*xx + yy*yy)/2.0; v = w*0.6667; if (v > 10000.0) v = 10000.0; /* It is necessary to use floor() in the conversion of xx*0.3 to an integer in the next statement in order to get the same value under valgrind. We know that xx is positive, so we don't need to test whether to use floor() or ceil(). Using the (int) cast only on a variable (not on a function) avoids a compiler warning. */ temp = floor(xx * 0.3); co = (above * (co + ((xx > 20000)? 6000 : (int)temp)) * main_stavemagn)/1000; f = ((double)start)/1000.0; g = ((double)stop)/1000.0; /* Preserve current coordinate system, translate and rotate so that the end points of the slur lie on the x-axis, symetrically about the origin. For ps_translate, the y value is relative to the stave base. Thereafter use ps_abspath() for absolute values. */ ps_gsave(); ps_translate((ix0+ix1+6*main_stavemagn)/2, (iy0+iy1)/2); ps_rotate(atan2(yy, xx)); /* Set up traditional Bezier coordinates for the complete slur. */ x0 = -w; x1 = v - w + (double)out_slurclx; x2 = w - v + (double)out_slurcrx; x3 = +w; y0 = 50.0; y1 = (double)(int)(co + out_slurcly); y2 = (double)(int)(co*wiggly + out_slurcry); y3 = 50.0; /* Calculate the coefficients for the original x parametric equation. */ ax = x3 - x0 + 3.0*(x1 - x2); bx = 3.0*(x2 - 2.0*x1 + x0); cx = 3.0*(x1 - x0); /* The given fractions are fractions along the line joining the two end points. These do not correspond linearly with the parameter t of the complete curve, so we have to calculate new fractional values. */ if (f > 0.0 && f < 1.0) f = bezfraction(f, ax, bx, cx, x0, x3); if (g > 0.0 && g < 1.0) g = bezfraction(g, ax, bx, cx, x0, x3); /* Now calculate the new Bezier point coordinates for the portion of the slur that we want, and set up the first path to be that portion. We used to compute the x values with just an (int) cast, but this gave slightly different values under valgrind. Using floor() or ceil() with a rounding value solves that problem. We must also avoid using an (int) cast directly on these functions, because it provokes a compiler warning when -Wbad-function-cast is set. */ zz[0] = x0 + ((ax*f + bx)*f + cx)*f; zz[1] = x0 + (((3.0*ax*g + bx)*f + 2.0*(bx*g + cx))*f + cx*g)/3.0; zz[2] = x0 + (((3.0*ax*g + 2.0*bx)*g + cx)*f + 2.0*cx*g + bx*g*g)/3.0; zz[3] = x0 + ((ax*g + bx)*g + cx)*g; temp = (zz[0] >= 0.0)? floor(zz[0] + 0.0001) : ceil(zz[0] - 0.0001); x[0] = (int)temp; temp = (zz[1] >= 0.0)? floor(zz[1] + 0.0001) : ceil(zz[1] - 0.0001); x[1] = (int)temp; temp = (zz[2] >= 0.0)? floor(zz[2] + 0.0001) : ceil(zz[2] - 0.0001); x[2] = (int)temp; temp = (zz[3] >= 0.0)? floor(zz[3] + 0.0001) : ceil(zz[3] - 0.0001); x[3] = (int)temp; /* Now do exactly the same for the y points */ ay = y3 - y0 + 3.0*(y1 - y2); by = 3.0*(y2 - 2.0*y1 + y0); cy = 3.0*(y1 - y0); zz[0] = y0 + ((ay*f + by)*f + cy)*f; zz[1] = y0 + (((3.0*ay*g + by)*f + 2.0*(by*g + cy))*f + cy*g)/3.0; zz[2] = y0 + (((3.0*ay*g + 2.0*by)*g + cy)*f + 2.0*cy*g + by*g*g)/3.0; zz[3] = y0 + ((ay*g + by)*g + cy)*g; temp = (zz[0] >= 0.0)? floor(zz[0] + 0.0001) : ceil(zz[0] - 0.0001); y[0] = (int)temp; temp = (zz[1] >= 0.0)? floor(zz[1] + 0.0001) : ceil(zz[1] - 0.0001); y[1] = (int)temp; temp = (zz[2] >= 0.0)? floor(zz[2] + 0.0001) : ceil(zz[2] - 0.0001); y[2] = (int)temp; temp = (zz[3] >= 0.0)? floor(zz[3] + 0.0001) : ceil(zz[3] - 0.0001); y[3] = (int)temp; cc[0] = path_move; cc[1] = path_curve; /* Deal with dashed slurs. The only way to do a decent job is to calculate the actual length of the slur. This has to be done the hard way by numerically integrating along the path, as the formulae don't give an analytic answer. To make sure that a full dash ends the slur when there are gaps in the slur, arrange for the slur to be drawn backwards if it doesn't start at the beginning, but does end at the end. (We could compute separate dashes for the individual parts, but that would probably look odd. Slurgaps in dashes slurs should be most rare, anyway.) */ if ((flags & sflag_i) != 0) { int dashcount, dashlength, gaplength, thickness, length; double dlength = 0.0; double xp = x0; double yp = y0; double t; /* Compute the curve length by integration. */ for (t = 0.0; t < 1.04; t += 0.05) { double xxc = ((ax*t + bx)*t + cx)*t + x0; double yyc = ((ay*t + by)*t + cy)*t + y0; dlength += sqrt((xxc-xp)*(xxc-xp) + (yyc-yp)*(yyc-yp)); xp = xxc; yp = yyc; } length = (int)dlength; /* Choose a dash length, spacing parameter, and line thickness */ if ((flags & sflag_idot) == 0) { dashlength = length/14; if (dashlength < 3000) dashlength = 3000; gaplength = (dashlength * 875)/1000; thickness = 500; } else { dashlength = 100; gaplength = ((length < 20000)? 3 : 4) * main_stavemagn; thickness = main_stavemagn; } /* Compute the number of dashes; if greater than one, compute the accurate gaplength and set dashing. */ dashcount = (length + gaplength + (dashlength + gaplength)/2) / (dashlength + gaplength); if (dashcount > 1) { gaplength = (length - dashcount * dashlength)/(dashcount - 1); ps_setdash(dashlength, gaplength, ((flags & sflag_idot) == 0)? caj_butt : caj_round); } /* Invert drawing order of partial curve that ends at the full end */ if (start > 0 && stop == 1000) { int tt; tt = x[0]; x[0] = x[3]; x[3] = tt; tt = x[1]; x[1] = x[2]; x[2] = tt; tt = y[0]; y[0] = y[3]; y[3] = tt; tt = y[1]; y[1] = y[2]; y[2] = tt; } /* Draw the dashed curve, and set editorial line adjustment to zero. */ cc[2] = path_end; ps_abspath(x, y, cc, thickness); ps_setdash(0, 0, caj_butt); ed_adjust = 0; } /* Deal with a non-dashed slur. For the other boundary of the slur, only the y coordinates of the control points change. */ else { double aay, bby, ccy; ed_adjust = (9*main_stavemagn)/10; x[4] = x[3]; x[5] = x[2]; x[6] = x[1]; x[7] = x[0]; y0 = -50.0; y1 = (double)(int)(co + above*main_stavemagn + out_slurcly); y2 = (double)(int)(co*wiggly + above*main_stavemagn + out_slurcry); y3 = -50.0; aay = y3 - y0 + 3.0*(y1 - y2); bby = 3.0*(y2 - 2.0*y1 + y0); ccy = 3.0*(y1 - y0); y[7] = (int)(y0 + ((aay*f + bby)*f + ccy)*f); y[6] = (int)(y0 + (((3.0*aay*g + bby)*f + 2.0*(bby*g + ccy))*f + ccy*g)/3.0); y[5] = (int)(y0 + (((3.0*aay*g + 2.0*bby)*g + ccy)*f + 2.0*ccy*g + bby*g*g)/3.0); y[4] = (int)(y0 + ((aay*g + bby)*g + ccy)*g); cc[2] = path_line; cc[3] = path_curve; x[8] = x[0]; y[8] = y[0]; cc[4] = path_line; cc[5] = path_end; /* Fill the path (thickness = -1) */ ps_abspath(x, y, cc, -1); } /* Deal with editorial slurs - only draw the mark when drawing the middle portion. */ if ((flags & sflag_e) != 0 && start < 500 && stop > 500) { int xxm, yym; double dx, dy, theta, cs, sn; /* Calculate the midpoint of the curve from the parametric equations taking t = 0.5, and also calculate the slope at that point. */ xxm = (int)(((ax*0.5 + bx)*0.5 + cx)*0.5 - w); dx = (3.0*ax*0.5 + 2.0*bx)*0.5 + cx; yym = (int)(((ay*0.5 + by)*0.5 + cy)*0.5); dy = (3.0*ay*0.5 + 2.0*by)*0.5 + cy; /* Draw the editorial mark. Translate and rotate gave rounding errors, so do it by steam. */ theta = atan2(dy, dx); cs = cos(theta); sn = sin(theta); if (above > 0) { x[0] = xxm - (int)((2000.0+(double)ed_adjust)*sn); y[0] = yym + (int)((2000.0+(double)ed_adjust)*cs); x[1] = xxm + (int)(2000.0*sn); y[1] = yym - (int)(2000.0*cs); } else { x[0] = xxm - (int)(2000.0*sn); y[0] = yym + (int)(2000.0*cs); x[1] = xxm + (int)((2000.0+(double)ed_adjust)*sn); y[1] = yym - (int)((2000.0+(double)ed_adjust)*cs); } cc[0] = path_move; cc[1] = path_line; cc[2] = path_end; ps_abspath(x, y, cc, 400); } /* Restore the former coordinate system. */ ps_grestore(); } /* End of setslur.c */