/************************************************* * 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 splitting the music bars into systems and making up pages from them. */ #include "pmwhdr.h" #include "pagehdr.h" enum { page_state_newmovt, page_state_newsystem, page_state_insystem, page_state_donesystem, page_state_donemovt }; /************************************************* * Pagination function * *************************************************/ /* This function is called when the music has been successfully read in to memory. Arguments: none Returns: nothing */ void paginate_go(void) { int i; int nextbarwidth; int xposition; int adjustkeyposition; int adjusttimeposition; int lengthwarn = 0; int page_layoutptr = 0; int save_notespacing[8]; int page_layoutstack[20]; int page_layoutstackptr = 0; int page_state = page_state_newmovt; BOOL page_done = FALSE; DEBUG(("paginate_go() start\n")); /* Font initialization */ font_reset(); /* To set no transformation */ font_xstretch = 0; /* No justification */ /* Set up page and line lengths in magnified units, and compute the total number of bars to be set. Once we have the line length, we can split and justify heading and footing lines. */ main_pagelength = (main_truepagelength * 1000)/main_magnification; main_totalbars = 0; for (i = 1; i <= main_lastmovement; i++) { curmovt = movement[i]; curmovt->linelength = (curmovt->truelinelength * 1000)/main_magnification; main_totalbars += curmovt->barcount; page_justifyheading(curmovt->heading); page_justifyheading(curmovt->footing); page_justifyheading(curmovt->lastfooting); } /* Get store for the three data structures */ page_accepteddata = store_Xget(sizeof(pagedatastr)); page_nextdata = store_Xget(sizeof(pagedatastr)); page_previousdata = store_Xget(sizeof(pagedatastr)); /* Other initialization */ page_barcount = 0; page_movtnumber = 1; /* Set up the first page block */ curpage = main_pageanchor = store_Xget(sizeof(pagestr)); curpage->next = NULL; curpage->number = main_lastpage = main_firstpage; curpage->spaceleft = main_pagelength; curpage->overrun = 0; curpage->sysblocks = NULL; curpage->footing = NULL; page_sysprevptr = &(curpage->sysblocks); page_countsystems = 0; page_lastsystem = NULL; page_movtpending = FALSE; curmovt = movement[page_movtnumber]; page_footing = page_footnotes = NULL; page_footnotedepth = 0; /* Set the spreading parameters for the first page */ page_botmargin = curmovt->botmargin; page_justify = curmovt->justify; page_topmargin = curmovt->topmargin; /* Working clefs while measuring. The current clef is needed only for measuring special keys, which may depend on the clef. We can't use the page_cont versions because they are not updated until after we know which bars are to be included in this system. */ page_sysclef = store_Xget(sizeof(uschar) * (MAX_STAVE + 1)); /* Loop that does the job; page_state controls which action is taken. */ while (!page_done) switch(page_state) { /****************************************************************************/ /* Deal with the start of a movement. We deal with the heading lines and then set up for paginating the rest of the movement. */ case page_state_newmovt: format_movt = curmovt; if (curmovt->barlinespace == (int)0x80000000) { page_barlinewidth = (curmovt->notespacing)[minim]/2 - 5000; if (page_barlinewidth < 3000) page_barlinewidth = 3000; curmovt->barlinespace = page_barlinewidth; } else page_barlinewidth = curmovt->barlinespace; memcpy(page_stavemap, curmovt->staves, STAVE_BITVEC_SIZE*sizeof(int)); page_ulaysize = ((curmovt->fontsizes)->fontsize_text)[ff_offset_ulay]; page_olaysize = ((curmovt->fontsizes)->fontsize_text)[ff_offset_olay]; /* Deal with heading texts if we know the movement is to go on this page. If we are at the top of a page, do the page heading unless it has been turned off explicitly. Also, set up a footing for this page. */ if (!page_movtpending) { if (page_movtnumber > 1 && curmovt->pageheading != NULL && (curmovt->movt_opt & movt_nopageheading) == 0 && curpage->spaceleft == main_pagelength) page_dopageheading(curmovt->pageheading); if (curmovt->heading != NULL) page_dopageheading(curmovt->heading); if (curmovt->footing != NULL) page_footing = curmovt->footing; } /* Create vector of per-bar data structures */ curmovt->posvector = (barposstr *)store_Xget(curmovt->barcount * sizeof(barposstr)) - 1; /* Now set up to process the bars. Start by finding the highest possible stave number. If no staves are present in the movement, the result is -1. */ page_lastwanted = -1; { usint x; for (i = STAVE_BITVEC_SIZE - 1; i >= 0; i--) { if ((x = curmovt->staves[i]) != 0) { page_lastwanted += i << 5; break; } } while (x != 0) { page_lastwanted++; if (page_lastwanted >= curmovt->laststave) break; x = x >> 1; } } /* Cut back the count of staves to those requested */ curmovt->laststave = page_lastwanted; /* Initialize values in the accepted data structure */ for (i = 0; i < STAVE_BITVEC_SIZE; i++) page_accepteddata->notsuspend[i] = curmovt->staves[i] & (~curmovt->suspend[i]); memcpy(page_accepteddata->notespacing, curmovt->notespacing, 8*sizeof(int)); page_accepteddata->stavenames = store_Xget((page_lastwanted+1)*sizeof(uschar *)); for (i = 1; i <= page_lastwanted; i++) { snamestr *sn = ((curmovt->stavetable)[i])->stave_name; page_accepteddata->stavenames[i] = sn; } /* Stavespacing is set only if it has been mentioned. If not, set the default. */ if (curmovt->stave_spacing == NULL && page_lastwanted >= 0) { page_ssnext = store_Xget((page_lastwanted+1)*sizeof(int)); page_ssnext[0] = 0; /* No space for stave 0 */ for (i = 1; i <= page_lastwanted; i++) page_ssnext[i] = 44000; } else page_ssnext = curmovt->stave_spacing; page_sgnext = curmovt->systemgap; /* Create vectors for {und,ov}erlay level handling */ page_ulevel = store_Xget((page_lastwanted+1)*sizeof(int)); page_ulhere = store_Xget((page_lastwanted+1)*sizeof(int)); page_olevel = store_Xget((page_lastwanted+1)*sizeof(int)); page_olhere = store_Xget((page_lastwanted+1)*sizeof(int)); for (i = 0; i <= page_lastwanted; i++) page_ulevel[i] = page_olevel[i] = BIGNUMBER; /* The ensure spacing data is often NULL, but if not, set up separate "here" and "next" vectors. */ page_ssenext = curmovt->stave_ensure; page_ssehere = (page_ssenext == NULL)? NULL : store_Xget((page_lastwanted+1)*sizeof(int)); /* Set up the continuation data vector, in the default state */ page_cont = store_Xget((page_lastwanted+1)*sizeof(contstr)); for (i = 0; i <= page_lastwanted; i++) { contstr *p = page_cont + i; p->slurs = NULL; p->hairpin = NULL; p->nbar = NULL; p->ulay = NULL; p->tie = NULL; p->overbeam = NULL; p->tiex = 0; p->noteheadstyle = nh_normal; p->flags = cf_default; p->clef = clef_treble; p->time = curmovt->time; p->key = transpose_key(curmovt->key, curmovt->transpose); } /* Final set up */ if (curmovt->startnotime) mac_initstave(page_showtimes, 0); else { mac_initstave(page_showtimes, ~0); mac_clearstave(page_showtimes, 0); } page_firstsystem = TRUE; page_startline = curmovt->startline; page_state = page_state_newsystem; /* Set up for fixed layout if required */ if (curmovt->layout == NULL) page_layoutptr = -1; else { page_layoutstack[0] = BIGNUMBER; page_layoutstackptr = 1; page_layoutptr = 0; while (curmovt->layout[page_layoutptr++] == lv_repeatcount) page_layoutstack[page_layoutstackptr++] = curmovt->layout[page_layoutptr++]; } /* The left and right justification bits can be set immediately, so that they apply to all systems in the new movement. The top and bottom bits can't be changed yet, because we don't know if this movement will start on the current page. */ page_justifyLR = curmovt->justify; /* Initialize the bar number and deal with the case of no bars of music in the movement. */ if ((page_barnumber = 1) > curmovt->barcount) { page_botmargin = curmovt->botmargin; /* Normally set when we know */ page_justify = curmovt->justify; /* that the first system fits */ page_topmargin = curmovt->topmargin; /* on the page. This code copes */ page_state = page_state_donemovt; /* with a single movement file */ } break; /****************************************************************************/ /* Deal with the start of a new system. There will always be at least one bar left when control gets here. */ case page_state_newsystem: { int keyxposition = 0; int timexposition = 0; int timewidth = 0; int Cmajor = TRUE; page_newpagewanted = FALSE; /* initialize flags */ page_layout_stretchn = page_layout_stretchd = 1; /* first time; no stretch */ if (page_ssenext != NULL) memcpy(page_ssehere, page_ssenext, (page_lastwanted+1)*sizeof(int)); /* If page_ssenext is NULL, but page_sshere is not, it means that page_ssehere was set up in the previous system by [ssabove]. */ else if (page_ssehere != NULL) { store_free(page_ssehere); page_ssehere = NULL; } /* Get a new system block and initialize */ page_sysblock = store_Xget(sizeof(sysblock)); page_sysblock->next = NULL; page_sysblock->type = sh_system; page_sysblock->flags = 0; page_sysblock->movt = curmovt; memcpy(page_sysblock->notsuspend, page_accepteddata->notsuspend, STAVE_BITVEC_SIZE*sizeof(int)); mac_initstave(page_sysblock->showtimes, 0); page_sysblock->barstart = page_barnumber; page_sysblock->barend = page_barnumber; page_sysblock->stavenames = page_accepteddata->stavenames; page_sysblock->stavespacing = page_ssnext; page_sysblock->systemgap = page_sgnext; page_sysblock->cont = misc_copycontstr(page_cont, page_lastwanted, TRUE); /* Set up working clefs at start of system; these are needed only for use when measuring the widths of special key signatures, which may depend on the clef. We can't use the page_cont values because they don't get updated dynamically ('cause we don't want to go past the bars we accept). */ for (i = 1; i <= page_lastwanted; i++) page_sysclef[i] = page_sysblock->cont[i].clef; page_sysblock->ulevel = store_Xget((page_lastwanted+1) * sizeof(int)); page_sysblock->olevel = store_Xget((page_lastwanted+1) * sizeof(int)); for (i = 0; i <= page_lastwanted; i++) { contstr *c = page_cont + i; if (c->overbeam != NULL) /* Clear continued beams for next system */ { store_free(c->overbeam); c->overbeam = NULL; } page_ulhere[i] = page_olhere[i] = 0; page_sysblock->ulevel[i] = -page_ulaysize - 1000; page_sysblock->olevel[i] = 20000; } /* Update the current key/time signatures if necessary */ page_setsignatures(); /* Initialize those fields of the current data structure that are reset for each system */ page_accepteddata->endkey = FALSE; page_accepteddata->endtime = FALSE; page_accepteddata->endbar = page_barnumber - 1; page_accepteddata->count = 0; /* Save the initial notespacing so that it can be restored for re-spacing bars when there's a big stretch factor. */ memcpy(save_notespacing, page_accepteddata->notespacing, 8*sizeof(int)); /* If all staves are suspended (can happen during part extraction with the use of [newline] or with S! bars) unsuspend the lowest numbered stave. If any staves get resumed in the system, this gets undone again later. */ if (!mac_anystave(page_sysblock->notsuspend)) for (i = 0; i < STAVE_BITVEC_SIZE; i++) { if (curmovt->staves[i] != 0) { page_sysblock->notsuspend[i] = curmovt->staves[i] & (-curmovt->staves[i]); break; } } /* Find the starting position of the stave */ page_accepteddata->startxposition = page_startwidth(page_accepteddata, page_stavemap, page_sysblock->notsuspend); /* Compute position for key signatures. We make them all line up vertically. The top bit in the stavelines value indicates no clefs are ever printed. This is to support the old [percussion] directive (which pre-dates [noclef]). For the moment, the value generated is relative to startxposition. */ for (i = 1; i <= page_lastwanted; i++) { stavestr *ss = curmovt->stavetable[i]; if (mac_teststave2(page_stavemap, page_sysblock->notsuspend, i) && (!ss->omitempty || (ss->barindex)[page_barnumber] != NULL)) { mac_setstavesize(i); if ((curmovt->stavetable[i])->stavelines < 127) { int clef = (page_sysblock->cont[i]).clef; int xpos = curmovt->clefwidths[clef] * main_stavemagn + page_startline->clefspace; if (xpos > keyxposition) keyxposition = xpos; } } } /* Compute the position for time signatures after the widest key signature (again relative to startxposition). Again, the top stavelines bit suppresses key signatures. */ for (i = 1; i <= page_lastwanted; i++) { if (mac_teststave2(page_stavemap, page_sysblock->notsuspend, i)) { mac_setstavesize(i); if ((curmovt->stavetable[i])->stavelines < 127) { int key = (page_sysblock->cont[i]).key; int xpos = keyxposition + (main_stavemagn * misc_keywidth(key, page_sysblock->cont[i].clef))/1000; if (xpos > timexposition) timexposition = xpos; if (key != 2) Cmajor = FALSE; } } } /* If at least one key signature is not C major, insert extra space before the key position. */ if (!Cmajor) { keyxposition += page_startline->keyspace; timexposition += page_startline->keyspace; } page_sysblock->keyxposition = keyxposition; /* If all staves are [percussion] staves, no clefs or keys get written. This can also happen for [noclef] staves with no key signature. In this case we need to put a little bit of space before the time signature or first note. */ if (timexposition == 0) timexposition += 2000; /* We are now at the position for the time signature, or first note if there is no time signature. */ if (mac_anystave(page_showtimes)) { timexposition += page_startline->timespace; for (i = 1; i <= page_lastwanted; i++) { if (mac_teststave2(page_stavemap, page_sysblock->notsuspend, i)) { int tw; mac_setstavesize(i); tw = (main_stavemagn * misc_timewidth((page_sysblock->cont[i]).time))/1000; if (tw > timewidth) timewidth = tw; } } memcpy(page_sysblock->showtimes, page_showtimes, STAVE_BITVEC_SIZE * sizeof(int)); mac_initstave(page_showtimes, 0); } page_sysblock->timexposition = timexposition; /* Set the first note position */ page_sysblock->firstnoteposition = timexposition + timewidth + PAGE_LEFTBARSPACE + page_startline->notespace; /* The xposition is an absolute position, used during calculations */ page_accepteddata->xposition = page_accepteddata->startxposition + page_sysblock->firstnoteposition; /* Initialize start of line flags and accidental space insertion flag */ page_startlinebar = TRUE; /* becomes FALSE after one acceptance */ page_lastendwide = FALSE; /* at start of line */ page_lastenddouble = FALSE; /* ditto */ /* Enter the mid-system state */ page_state = page_state_insystem; } break; /****************************************************************************/ /* In the middle of a system - measure the next bar and see if it will fit. It it doesn't, and it starts with a key or time signature and there is not even enough room for that, we have to back off from accepting the previous bar. When the system is full, change state again. */ case page_state_insystem: /* Make copy of current status for MakePosTable to update */ *page_nextdata = *page_accepteddata; /* Measure the bar -- this also sets various flags such as page_warnkey and also sets page_xxwidth if there is a key and/or time signature. The lengthwarn variable is normally zero (or less), but is set to 1 after the end of a system so that re-measuring the bar, for the next system, doesn't give a length warning again. If we back up two bars, in order to fit in a key/time signature, lengthwarn is set to 2. */ nextbarwidth = page_makepostable(lengthwarn-- < 1); /* Compute position if bar were accepted */ xposition = page_accepteddata->xposition + nextbarwidth; /* If a stave has been resumed in this bar, or if a stave name has changed, it may be necessary to change the width of the stave name space (i.e. startxposition). This may make the bar unacceptable. We must also check the clef of the resumed stave, if it has a bar to print at the start of the system, because a wider clef will alter the position of the key signature. Likewise the key signature of the resumed stave, because a wider key signature will alter the position of the time signature. */ adjustkeyposition = 0; adjusttimeposition = 0; if (mac_diffstave(page_nextdata->notsuspend, page_accepteddata->notsuspend) || page_nextdata->stavenames != page_accepteddata->stavenames) { int newkeyxposition = 0; int newtimexposition = 0; /* Deal with stave names */ int newstartx = page_startwidth(page_nextdata, page_stavemap, page_nextdata->notsuspend); page_nextdata->startxposition = newstartx; xposition += newstartx - page_accepteddata->startxposition; /* Deal with change of key signature position */ for (i = 1; i <= page_lastwanted; i++) { stavestr *ss = curmovt->stavetable[i]; if (mac_teststave2(page_stavemap, page_nextdata->notsuspend, i) && (!ss->omitempty || (ss->barindex)[page_sysblock->barstart] != NULL)) { mac_setstavesize(i); if ((curmovt->stavetable[i])->stavelines < 127) { int clef = (page_sysblock->cont[i]).clef; int key = (page_sysblock->cont[i]).key; int xp = curmovt->clefwidths[clef] * main_stavemagn + page_startline->clefspace; if (xp > newkeyxposition) newkeyxposition = xp; xp = newkeyxposition + (main_stavemagn * misc_keywidth(key, clef))/1000; if (xp > newtimexposition) newtimexposition = xp; } } } if (newkeyxposition > page_sysblock->keyxposition) { adjustkeyposition = newkeyxposition - page_sysblock->keyxposition; xposition += adjustkeyposition; } if (newtimexposition > page_sysblock->timexposition + adjustkeyposition) { adjusttimeposition = newtimexposition - page_sysblock->timexposition - adjustkeyposition; xposition += adjusttimeposition; } } /* Default overrun is "infinity" */ page_sysblock->overrun = 255; /* If this is not the first bar on the line, see if it will fit. We always accept one bar - it gets squashed (with a warning). If a fixed layout has been specified, accept bars until we have one more than required. */ if ((page_layoutptr < 0 && page_accepteddata->count > 0 && xposition > curmovt->linelength) || (page_layoutptr >= 0 && page_accepteddata->count >= curmovt->layout[page_layoutptr])) { int overrun = xposition - curmovt->linelength; lengthwarn = 1; /* Don't warn when we reprocess the bar */ /* See if cautionary signature(s) are needed. */ if (!page_startlinebar && (page_warnkey || page_warntime)) { xposition = page_accepteddata->xposition + page_xxwidth; /* If there is not even enough space for the end-of-line signature(s), back up to the previous bar, unless there isn't one to back up to, in which case give an overlong line warning. */ if (page_layoutptr < 0 && xposition > curmovt->linelength) { if (page_accepteddata->count > 1) { pagedatastr *temp = page_previousdata; page_previousdata = page_accepteddata; page_accepteddata = temp; page_barcount -= page_lastbarcountbump; page_barnumber -= page_lastbarcountbump; overrun = xposition - curmovt->linelength; lengthwarn++; /* Two bars not to warn for */ } else { error_moan(ERR56, page_previousdata->endbar); overrun = 25500; } } /* Room for key/time -- set flag(s) and xposition */ else { page_accepteddata->xposition = xposition + page_barlinewidth; page_accepteddata->endkey = page_warnkey; page_accepteddata->endtime = page_warntime; } } /* Set up the overrun value as a whole number of points, rounded up */ overrun = (overrun + 999)/1000; if (overrun > 255) overrun = 255; page_sysblock->overrun = (uschar)overrun; /* In all cases we've finished the system */ page_state = page_state_donesystem; } /* There is room on the line for this bar, or it is the first bar on the line, so accept it, first freeing any vectors that previous points to which are no longer needed. Give a warning for an over- flowing single bar, which will be squashed to fit. */ else { pagedatastr *temp = page_previousdata; page_previousdata = page_accepteddata; page_accepteddata = page_nextdata; page_nextdata = temp; if (page_accepteddata->count == 0 && xposition > curmovt->linelength) { error_moan(ERR55, page_accepteddata->endbar + 1, nextbarwidth, xposition-nextbarwidth, curmovt->linelength); } /* If this bar starts with a key or time change, then bump the previous x position to account for it, as that will be what happens if we have to back off. */ if (page_xxwidth > 0 && !page_startlinebar) { page_previousdata->xposition += page_xxwidth + page_barlinewidth; page_previousdata->endkey = page_warnkey; page_previousdata->endtime = page_warntime; } else { page_previousdata->endkey = FALSE; page_previousdata->endtime = FALSE; } /* Allow for key & time signature positioning adjustment */ if (adjustkeyposition || adjusttimeposition) { page_sysblock->keyxposition += adjustkeyposition; page_sysblock->timexposition += adjustkeyposition + adjusttimeposition; page_sysblock->firstnoteposition += adjustkeyposition + adjusttimeposition; } /* Update xposition, endbar number, and counts */ page_accepteddata->xposition = xposition + page_barlinewidth; if (page_manyrest >= 2) { page_accepteddata->endbar += page_manyrest; page_barcount += page_manyrest; /* total count */ page_barnumber += page_manyrest; page_lastbarcountbump = page_manyrest; /* for backing off */ } else { page_accepteddata->endbar++; page_barcount++; page_barnumber++; page_lastbarcountbump = 1; } page_accepteddata->count += 1; /* counts printed bars */ page_startlinebar = FALSE; if (page_barnumber > curmovt->barcount) page_state = page_state_donesystem; } break; /****************************************************************************/ /* Completed a system - tidy the data structures and see if it fits onto the current page. The positions at the start of the line can be made absolute now that startxposition is known. We also perform the stretching operation on the position tables in the bars at this point. */ case page_state_donesystem: { int startxposition = page_accepteddata->startxposition; int barlinewidth = page_barlinewidth; int justbits, stretchn, stretchd, sysdepth, sysfootdepth; page_sysblock->stavenames = page_accepteddata->stavenames; page_sysblock->barend = page_accepteddata->endbar; /* If there are no unsuspended staves in the accepted data, then leave the sysblock alone, as it will have had one stave forced into it. Otherwise, overwrite with the accepted value, thereby turning off the fudged stave if there was one. */ if (mac_anystave(page_accepteddata->notsuspend)) for (i = 0; i < STAVE_BITVEC_SIZE; i++) page_sysblock->notsuspend[i] = page_accepteddata->notsuspend[i]; /* Fix various initial positions */ page_sysblock->startxposition = startxposition; page_sysblock->joinxposition = startxposition; page_sysblock->keyxposition += startxposition; page_sysblock->timexposition += startxposition; page_sysblock->firstnoteposition += startxposition; if (page_accepteddata->endkey) page_sysblock->flags |= sysblock_warnkey; if (page_accepteddata->endtime) page_sysblock->flags |= sysblock_warntime; /* Advance the continuation data to the end of the system, ready for the next one. This scan also handles changes of stave and system spacing, and local justification. Because we do not yet know if this system is going to fit on the page, any vertical justification changes that it makes are placed in page_sys_xxx variables. We initialize them to negative numbers to detect changes. There is an unfortunate chicken-and-egg situation here. We need to set the barlinewidth, as it is used when computing beam slopes for beams that cross barlines. It should really be set to the stretched value, but we can't compute the stretching factor until we've done the barcont stuff, in order to know if we have to justify or not. We cheat by setting it to the unstretched value and hoping that is near enough... */ page_sysblock->barlinewidth = barlinewidth; page_sys_topmargin = page_sys_botmargin = page_sys_justify = -1; /* The page_setcont() routine also collects footnotes and system notes. We initialize the various variables before calling it. */ page_newfootnotes = NULL; page_newfootnotedepth = 0; page_setcont(); /* Set up the xposition of the end of the line and the justify bits, and compute the spreading parameters. We spread if the line is wider than a proportion of the linewidth as set by the stretchthresh variables, or if it's too long (when the "spreading" is actually squashing). Note that left/right justification bits are taken from this system's flags, if there were any. */ xposition = page_accepteddata->xposition - barlinewidth; justbits = page_justifyLR & (just_left + just_right); /* Left + right justification */ if ((justbits == just_left + just_right && xposition - page_sysblock->startxposition > (main_stretchthreshnum* (curmovt->linelength - page_sysblock->startxposition)) / main_stretchthreshden) || xposition > curmovt->linelength) { int save_xxwidth = page_xxwidth; int xxadjust = page_sysblock->firstnoteposition + ((page_accepteddata->endkey || page_accepteddata->endtime)? page_xxwidth + barlinewidth : 0); stretchn = curmovt->linelength - xxadjust; stretchd = xposition - xxadjust; page_sysblock->xjustify = 0; page_sysblock->flags |= sysblock_stretch; /* If the stretching factor is large enough, throw away the position tables and re-format all the bars using the known stretching factor. They should not get any wider. Then compute revised stretching factors. Repeat if necessary, up to 4 times. Note that we have to keep re-stretching the barlinewidth. From release 4.22 we also do this when the stretching is actually squashing by a large enough amount, which can happen when the layout directive forces more bars onto a line than would normally fit. This is necessary when there is underlay, where words might crash when a bar is squashed. (While testing this, it turns out that the squashing version also sometimes kicks in after a stretching time round the loop, which sometimes overdoes things, it seems. It does no harm.) There's an option to prevent this new logic from happening, for old files. It currently selects between three states: do nothing, do it only for over-stretching, do it for both over-stretching and squashing. */ page_layout_stretchn = stretchn; page_layout_stretchd = stretchn; /* sic - see below - it changes cumulatively */ i = 0; while (i++ < 4 && ( (mac_muldiv(stretchd, 1000, stretchn) > main_stretchrespacethresh && opt_stretchrule >= 2) || (mac_muldiv(stretchn, 1000, stretchd) > main_stretchrespacethresh && opt_stretchrule >= 1) )) { int j; page_layout_stretchd = mac_muldiv(page_layout_stretchd, stretchd, stretchn); xposition = page_sysblock->firstnoteposition; page_startlinebar = TRUE; /* becomes FALSE after one acceptance */ page_lastendwide = FALSE; /* at start of line */ page_lastenddouble = FALSE; /* ditto */ barlinewidth = mac_muldiv(barlinewidth, stretchn, stretchd); memcpy(page_nextdata->notespacing, save_notespacing, 8*sizeof(int)); /* Reset clefs at system start */ for (j = 1; j <= page_lastwanted; j++) page_sysclef[j] = page_sysblock->cont[j].clef; for (page_barnumber = page_sysblock->barstart; page_barnumber <= page_sysblock->barend; page_barnumber++) { barposstr *bp = curmovt->posvector + page_barnumber; store_free(bp->vector); xposition += page_makepostable(FALSE) + barlinewidth; page_startlinebar = FALSE; if (page_manyrest >= 2) page_barnumber += page_manyrest - 1; } xposition -= barlinewidth; if (page_accepteddata->endkey || page_accepteddata->endtime) xposition += save_xxwidth + curmovt->barlinespace; /* Unstretched barlinewidth */ stretchd = xposition - xxadjust; if (main_tracepos == (-1) || (page_sysblock->barstart <= main_tracepos && page_sysblock->barend >= main_tracepos)) debug_printf("i=%d bars %d-%d old=%d new=%d\n", i, page_sysblock->barstart, page_sysblock->barend, mac_muldiv(page_layout_stretchn, 1000, page_layout_stretchd), mac_muldiv(stretchn, 1000, stretchd)); } } /* Deal with right only or no justification */ else { stretchn = stretchd = 1; if ((justbits & just_left) == 0) { int xjustify = curmovt->linelength - xposition; if (justbits == 0) xjustify /= 2; page_sysblock->xjustify = xjustify; } else page_sysblock->xjustify = 0; } /* The barline width for the system is the final stretched value. */ page_sysblock->barlinewidth = mac_muldiv(barlinewidth, stretchn, stretchd); /* Now apply the stretching operation to the bars in the system. Key and time signatures and left repeats at the start of a bar are not stretched. Grace notes are kept at the same distance from their successors. */ for (i = page_sysblock->barstart; i <= page_sysblock->barend; i++) { barposstr *bp = curmovt->posvector + i; posstr *p = bp->vector; int count = bp->count; /* If this is the first bar of a multi-rest, make a correction to the value of i to skip the others. */ i += bp->multi - 1; /* Skip over any clefs, key signatures or time signatures at the start of the bar. For big stretches, it is in fact not enough to do this, as the stretched barlinewidth can make their positioning look silly. We move them to the left in this case. This is probably less relevant now that we re-lay-out lines to get the stretching factor down. */ /***** PRO TEM remove fix to retain previous state pending revised stretching. In this state, clefs are not tested here (they don't normally occur at line starts). We need a revised stretching algorithm to keep the first note fixed even after clefs, keys, and times. ****/ if ((p->moff <= posx_timelast && p->moff >= posx_keyfirst)) /*** || p->moff == posx_clef) ***/ { int n = 9; while ((count > 0 && p->moff >= posx_keyfirst && p->moff <= posx_timelast) || p->moff == posx_clef) { p->xoff -= ((page_sysblock->barlinewidth - page_barlinewidth)*n)/10; if (n > 2) n -= 2; p++; count--; } } /* Else skip over any grace notes and accidentals, and also the first note, which we do not want to move. But if there is nothing in the bar, don't skip over the first (= last) item. */ else { while (count > 0 && p->moff < 0) { p++; count--; } if (count > 1) { p++; count--; } } /* Now stretch the remaining items, dealing specially with grace notes, which are identified by finding the next full note and checking the offset. Also deal specially with clefs. */ while (count-- > 0) { int old; posstr *pp = p; while (count > 0 && (pp+1)->moff - pp->moff <= -posx_max) { pp++; count--; } old = pp->xoff; pp->xoff = mac_muldiv(pp->xoff, stretchn, stretchd); while (p < pp) { int d = -(pp->moff - p->moff); int rightmost = p->xoff + pp->xoff - old; int leftmost = mac_muldiv(p->xoff, stretchn, stretchd); /* Clef positions are stretched just a bit if they are the last thing in the bar. Otherwise, the position used is halfway between an unstretched and stretched position. */ if (d == posx_clef) p->xoff = (count == 0)? rightmost - (rightmost - leftmost)/5 : (rightmost+leftmost)/2; /* Grace notes are never stretched at all; other things are stretched a bit, but not the full amount. */ else if (d >= posx_gracefirst && d <= posx_gracelast) p->xoff = rightmost; else p->xoff = (rightmost + leftmost)/2; p++; } p++; } } /* If this was the first system of a movement, re-compute the stave name structure to use the second name. Also, if an indent is set for the brackets and braces, adjust the position of the joining signs. */ if (page_firstsystem) { int j; page_firstsystem = FALSE; page_accepteddata->stavenames = store_Xget((page_lastwanted+1)*sizeof(uschar *)); for (j = 1; j <= page_lastwanted; j++) { snamestr *sn = ((curmovt->stavetable)[j])->stave_name; page_accepteddata->stavenames[j] = (sn != NULL)? sn->next : NULL; } if (curmovt->startbracketbar >= page_sysblock->barstart && curmovt->startbracketbar < page_sysblock->barend) { int blw = 0; page_sysblock->joinxposition = page_sysblock->firstnoteposition; for (i = page_sysblock->barstart; i <= curmovt->startbracketbar; i++) { barposstr *bp = curmovt->posvector + i; posstr *p = bp->vector; page_sysblock->joinxposition += p[bp->count-1].xoff + blw; blw = page_sysblock->barlinewidth; } } } /* Check that the stavespacing vector conforms to the ensure values, and if not, make a new one that does. At the same time, compute the total depth of the system. If an unsuspended stave has a zero stave spacing, make sure that the following stave is not suspended. */ sysdepth = 0; for (i = 1; i <= page_lastwanted; i++) { if (mac_teststave2(page_stavemap, page_sysblock->notsuspend, i)) { int j = i; int next = i+1; while (j < page_lastwanted && page_sysblock->stavespacing[j++] == 0) { mac_setstave(page_sysblock->notsuspend, j); } while (next <= page_lastwanted && mac_testNstave2(page_stavemap, page_sysblock->notsuspend, next)) next++; if (next <= page_lastwanted) { if (page_ssehere != NULL && page_sysblock->stavespacing[i] < page_ssehere[next]) { if (page_sysblock->stavespacing == page_ssnext) page_sysblock->stavespacing = store_copy(page_ssnext); page_sysblock->stavespacing[i] = page_ssehere[next]; } sysdepth += page_sysblock->stavespacing[i]; } page_lastulevel = page_sysblock->ulevel[i]; } } page_sysblock->systemdepth = sysdepth; /* Compute a testing depth consisting of the system depth plus the total depth of any footnotes, and space below the current system. And space between the current footnotes and any new ones. */ sysfootdepth = sysdepth + page_footnotedepth + page_newfootnotedepth; if (page_footnotedepth + page_newfootnotedepth > 0) { sysfootdepth += - page_lastulevel; if (page_footnotedepth > 0 && page_newfootnotedepth > 0) sysfootdepth += curmovt->footnotesep; } /* If this system is deeper than the page depth, we can't handle it. After the error, it will cause a new page to be started, but it will never be printed. */ if (sysfootdepth > main_pagelength) { int overflow = sysfootdepth - main_pagelength; error_moan(ERR64, page_sysblock->barstart, page_movtnumber, overflow, (overflow == 1000)? "" : "s"); } /* If we have a new movement pending, find the depth of the headings and see if the headings plus this system will fit on the current page. If the system depth is zero, we have a single-stave system, in which case we insist on there being room for another one as well. */ if (page_movtpending) { headstr *h = curmovt->heading; int depth = (h == NULL)? 0 : 17000; while (h != NULL) { depth += h->space; h = h->next; } depth += (sysfootdepth == 0)? page_sysblock->systemgap : sysfootdepth; /* If no room, terminate the page and start a new one. We must arrange that footings are printed from the *previous* movement, but take the option for lastfooting and pageheading from the *current* movement. */ if (curpage->spaceleft < depth) { BOOL ph = ((curmovt->movt_opt) & movt_nopageheading) == 0; BOOL lf = ((curmovt->movt_opt) & movt_uselastfooting) != 0; curmovt = movement[page_movtnumber - 1]; page_endpage(lf); curmovt = movement[page_movtnumber]; page_newpage(curmovt->heading, ph? curmovt->pageheading : NULL); } /* There is room: output the new heading on this page, and set the justification parameters from the new movement. (The horizontal ones will have been set already, but the vertical ones can't be changed until the page is known.) We also change the bottom margin, but leave the top margin until the next page. */ else { page_justify = curmovt->justify; page_botmargin = curmovt->botmargin; if (curmovt->heading != NULL) { page_dopageheading(curmovt->heading); curpage->spaceleft -= 17000; } } /* Set up a new footing, if present. Note that if there isn't one, and we didn't start a new page, and there is one still set up from the previous movement already (in page_footing), then it will still get printed at the bottom of this page. */ if (curmovt->footing != NULL) page_footing = curmovt->footing; /* Cancel pending flag */ page_movtpending = FALSE; } /* If this system does not fit on the page, start a new one. */ if (curpage->spaceleft < sysfootdepth) { curpage->overrun = sysfootdepth - curpage->spaceleft; page_endpage(FALSE); page_newpage(NULL, curmovt->pageheading); } /* Connect the system to the chain and keep count of the number of vertically spreadable systems on the page. */ *page_sysprevptr = page_sysblock; page_sysprevptr = &(page_sysblock->next); page_countsystems++; /* If there were any footnotes, connect them to the page's footnote list for inclusion at the end. Save the current spacing value for use if the page does actually end here. */ if (page_newfootnotes != NULL) { if (page_footnotes == NULL) page_footnotes = page_newfootnotes; else { page_lastfootnote->next = page_newfootnotes; page_newfootnotes->spaceabove = curmovt->footnotesep; page_footnotedepth += curmovt->footnotesep; } page_lastfootnote = page_lastnewfootnote; page_footnotedepth += page_newfootnotedepth; } page_footnotespacing = - page_lastulevel; /* Update the space left on the page; just take off the space for the music (the system), not the footnotes. They'll be considered again with the next system. */ curpage->spaceleft -= sysdepth + page_sysblock->systemgap; page_state = page_state_newsystem; page_lastsystem = page_sysblock; /* Update the vertical justification parameters if they changed in this system. */ if (page_sys_justify != -1) page_justify = page_sys_justify; if (page_sys_topmargin != -1) page_topmargin = page_sys_topmargin; if (page_sys_botmargin != -1) page_botmargin = page_sys_botmargin; /* If we have an explicit layout, deal with advancing the pointer and checking for a forced new page. */ if (page_layoutptr >= 0) { page_layoutptr++; for (;;) { while (curmovt->layout[page_layoutptr] == lv_newpage) { page_layoutptr++; if (page_barnumber <= curmovt->barcount) page_newpagewanted = TRUE; } if (curmovt->layout[page_layoutptr] == lv_repeatptr) { if ((page_layoutstack[page_layoutstackptr-1] -= 1) > 0) page_layoutptr = curmovt->layout[page_layoutptr+1]; else { page_layoutstackptr--; page_layoutptr += 2; } } else { while (curmovt->layout[page_layoutptr++] == lv_repeatcount) page_layoutstack[page_layoutstackptr++] = curmovt->layout[page_layoutptr++]; break; } } } /* If a new page was forced after this system, set it up. This can only happen via [newpage] if there are more bars; hence it can't also be a movement end. Via explicit layout, it is also only set if there are more bars. */ if (page_newpagewanted) { page_endpage(FALSE); page_newpage(NULL, curmovt->pageheading); } /* Handle the end of a movement */ if (page_barnumber > curmovt->barcount) page_state = page_state_donemovt; } break; /****************************************************************************/ /* Completed a movement. Tidy the data structures and deal with the end of the whole piece or with starting a subsequent movement. */ case page_state_donemovt: misc_freecontstr(page_cont, page_lastwanted); if (page_ssehere != NULL) store_free(page_ssehere); store_free(page_ulevel); store_free(page_ulhere); store_free(page_olevel); store_free(page_olhere); /* End of the last movement */ if (page_movtnumber++ >= main_lastmovement) { page_endpage(TRUE); page_done = TRUE; } /* There is another movement to follow. If it contains no staves, we must deal with the headings here. */ else { movtstr *nextmovt = movement[page_movtnumber]; int movt_opt = (nextmovt->movt_opt & ~(movt_nopageheading | movt_uselastfooting)); /* Deal with the case of no staves in the movement; we must decide now whether it fits on the page or not if nothing is specified. */ if (nextmovt->barcount < 1 && movt_opt == movt_default) { headstr *h = nextmovt->heading; int depth = 0; while (h != NULL) { depth += h->space; h = h->next; } movt_opt = (curpage->spaceleft < depth)? movt_newpage : movt_thispage; } /* Handle forced new page; set page_heading NULL to prevent any heading output, which will be done by the start-of-movt code. */ if (movt_opt == movt_newpage) { page_endpage((nextmovt->movt_opt & movt_uselastfooting) != 0); curmovt = nextmovt; page_newpage(NULL, NULL); } /* If no page option was specified, we can't decide whether to start a new page until after the next system has been read. We just set a flag for the work to be done then. For the very special case of "thisline", we remove and vertical advance from the last system. Another system of the same depth will then always fit. We must also reduce the count of spreadable systems, since this one should not get additional space added to it! */ else { curmovt = nextmovt; if (movt_opt == movt_thisline && page_lastsystem != NULL) { curpage->spaceleft += page_lastsystem->systemdepth + page_lastsystem->systemgap; page_lastsystem->flags |= sysblock_noadvance; page_countsystems--; } if (movt_opt != movt_thispage) page_movtpending = TRUE; } /* Change state */ page_state = page_state_newmovt; } break; } store_free(page_accepteddata); store_free(page_nextdata); store_free(page_previousdata); DEBUG(("paginate_go() end\n")); } /* End of paginate.c */