chiark / gitweb /
Import upstream version 5.3.
[mup] / mup / mup / prntmisc.c
CommitLineData
69695f33
MW
1
2/* Copyright (c) 1995, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2006 by Arkkra Enterprises */
3/* All rights reserved */
4
5/* functions for printing endings, pedal marks, phrase marks, etc */
6
7#include "defines.h"
8#include "structs.h"
9#include "globals.h"
10
11/* width of tapered curve at endpoint relative to maximum width */
12#define TAPERWID (0.4)
13
14
15/*
16 * Define a structure for storing points of a curve, whether generated (like
17 * for phrase marks) or user-specified. It also stores information used in
18 * calculating Bezier curves that will be drawn between each pair of
19 * neighboring points. The "control points" are points 1 and 2 for the
20 * Bezier curve going from this point to the next point. These structures
21 * get their x and y filled in from CURVE or CRVLIST structures.
22 */
23struct CURVEINFO {
24 float x, y; /* point's coords */
25 float len; /* length of line segment from here to next point */
26 float ang; /* angle between the 2 segments at this point */
27 int bend; /* bend direction at point: 1=clockwise, -1=counter */
28 float slopetan; /* slope of line "tangent" to this point if axes are */
29 /* rotated such that segment starting here is horiz. */
30 float x1, y1; /* control point 1 for segment starting at this point */
31 float x2, y2; /* control point 2 for segment starting at this point */
32};
33
34/* static functions */
35static void do_endings P((struct MAINLL *first_p, struct MAINLL *last_p,
36 char *endlabel, int carryout));
37static void draw_ending P((int staffno, double ry, struct MAINLL *first_p,
38 struct MAINLL *last_p, char *endlabel, int carryout));
39static void pr_endlabel P((double x, double y, char *label));
40static void pr_end_line P((double begin_x, double end_x, double y,
41 int carryout));
42static int is_top_visible_in_range P((int staffno, int top));
43static void calccurve P((struct CURVEINFO v[], int num));
44static void findcontrol P((struct CURVEINFO v[], int num));
45
46\f
47
48/* whenever we hit a FEED, draw any endings associated with the score */
49
50void
51pr_endings(main_feed_p)
52
53struct MAINLL *main_feed_p; /* FEED */
54
55{
56 static char *endlabel = (char *) 0;/* ending label if had to carry over
57 * to next score */
58 struct MAINLL *curr_p; /* where we are in main list */
59 struct MAINLL *first_p; /* where an ending begins */
60 struct MAINLL *last_bar_p = 0; /* points to last bar on score so far */
61
62
63 debug(512, "pr_endings");
64
65 first_p = (struct MAINLL *) 0;
66
67 /* go through the entire score line. For every set of measures that have
68 * endings, draw them. */
69 for (curr_p = main_feed_p->next; curr_p != (struct MAINLL *) 0;
70 curr_p = curr_p->next) {
71
72 /* go just to end of current score */
73 if (curr_p->str == S_FEED) {
74 break;
75 }
76
77 /* if there is a pseudo bar, see if it is in an ending */
78 if (curr_p->str == S_CLEFSIG) {
79 if (curr_p->u.clefsig_p->bar_p != (struct BAR *) 0 &&
80 curr_p->u.clefsig_p->bar_p->endingloc
81 != NOITEM) {
82 first_p = curr_p;
83 endlabel = curr_p->u.clefsig_p->bar_p->endinglabel;
84 }
85 continue;
86 }
87
88 /* for each bar, check its endingloc and act accordingly */
89 else if (curr_p->str == S_BAR) {
90
91 switch(curr_p->u.bar_p->endingloc) {
92
93 case ENDITEM:
94 if (first_p == (struct MAINLL *) 0) {
95 pfatal("ending without beginning");
96 }
97 /*FALLTHRU*/
98 case NOITEM:
99 /* if we were doing an ending, we reached the
100 * end of it, so handle it */
101 if (first_p != (struct MAINLL *) 0) {
102 /* it doesn't seem like it should be
103 * possible to get inside this IF for
104 * the NOITEM case, but it doesn't
105 * hurt anything to have to code as it
106 * is, and I don't want to change it for
107 * fear it would break some obscure
108 * circumstance I've forgotten about */
109 do_endings(first_p, curr_p, endlabel, NO);
110 endlabel = (char *) 0;
111 first_p = (struct MAINLL *) 0;
112 }
113 break;
114
115 case STARTITEM:
116 /* if we are also implictly ending a previous
117 * ending, do that first. In any case, keep
118 * track of where this ending begins. */
119 if (first_p != (struct MAINLL *) 0) {
120 do_endings(first_p, curr_p,
121 endlabel, NO);
122 }
123
124 first_p = curr_p;
125 endlabel = curr_p->u.bar_p->endinglabel;
126
127 break;
128
129 case INITEM:
130 break;
131
132 default:
133 pfatal("bad endingloc value");
134 /*NOTREACHED*/
135 break;
136 }
137 last_bar_p = curr_p;
138 }
139 }
140
141 /* we must be at the end of the score. If we are in the middle
142 * of an ending, draw this score's portion of it now */
143 if ( (first_p != (struct MAINLL *) 0)
144 && (last_bar_p != (struct MAINLL *) 0)) {
145 do_endings(first_p, last_bar_p, endlabel,
146 last_bar_p->u.bar_p->endingloc
147 == INITEM ? YES : NO);
148 }
149}
150\f
151
152/* now that we have identified an ending, print it above each
153 * staff that is supposed to get endings */
154
155static void
156do_endings(first_p, last_p, endlabel, carryout)
157
158struct MAINLL *first_p; /* where to begin drawing endings */
159struct MAINLL *last_p; /* where to end the endings */
160char *endlabel; /* if ending has a label, this will be
161 * that label, otherwise NULL */
162int carryout; /* YES if will carry over to next staff */
163
164{
165 struct MARKCOORD *markc_p; /* info about where to draw ending */
166
167
168 /* for each staff that is supposed to have endings,
169 * draw the ending */
170 if (first_p->str == S_CLEFSIG) {
171 /* pseudo-bar */
172 markc_p = first_p->u.clefsig_p->bar_p->ending_p;
173 }
174 else {
175 /* normal bar */
176 markc_p = first_p->u.bar_p->ending_p;
177 }
178
179 /* if this is an endending and the bar line at the end is an ordinary
180 * bar or an invisbar, then set the carryout flag so that the final
181 * vertical line doesn't get drawn. */
182 if (last_p->u.bar_p->endingloc == ENDITEM
183 && (last_p->u.bar_p->bartype == SINGLEBAR ||
184 last_p->u.bar_p->bartype == INVISBAR)) {
185 /* But if this is the very end of the piece,
186 * then we *do* want to draw the final vertical */
187 struct MAINLL* mll_p;
188 for (mll_p = last_p->next; mll_p != 0; mll_p = mll_p->next) {
189 if (mll_p->str == S_STAFF) {
190 break;
191 }
192 }
193 if (mll_p != 0) {
194 carryout = YES;
195 }
196 }
197
198 /* draw an ending for each item in MARKCOORD list */
199 for ( ; markc_p != (struct MARKCOORD *) 0; markc_p = markc_p->next) {
200 draw_ending(markc_p->staffno, (double) markc_p->ry,
201 first_p, last_p, endlabel, carryout);
202 }
203}
204\f
205
206/* draw ending marks over specified staff */
207
208static void
209draw_ending(staffno, ry, first_p, last_p, endlabel, carryout)
210
211int staffno; /* which staff to draw over */
212double ry; /* relative y */
213struct MAINLL *first_p; /* draw ending starting from here */
214struct MAINLL *last_p; /* ending end here */
215char *endlabel; /* if has label, this is the label, else is NULL */
216int carryout; /* if YES, will carry over to next score */
217
218{
219 float begin_x; /* x coord of beginning and end of ending */
220 float y; /* vertical location of ending */
221
222
223 /* first_p can point to either a CLEFSIG (if carrying over an
224 * ending) or to a BAR. Find appropriate x coordinate */
225 switch (first_p->str) {
226
227 case S_CLEFSIG:
228 begin_x = first_p->u.clefsig_p->bar_p->c[AX];
229 break;
230
231 case S_BAR:
232 begin_x = first_p->u.bar_p->c[AX];
233 break;
234
235 default:
236 /* shut up compilers that erroneously thinks begin_x
237 * could get used without being set. */
238 begin_x = 0.0;
239 pfatal("bad struct type passed to draw_ending");
240 /*NOTREACHED*/
241 break;
242 }
243
244 /* get vertical position */
245 y = Staffs_y[staffno] + ry;
246
247 /* now we know where to put it, so draw it */
248 do_linetype(L_NORMAL);
249
250 /* print the beginning vertical and label now if appropriate */
251 if (endlabel != (char *) 0) {
252 pr_endlabel( (double) begin_x, (double) y, endlabel);
253 }
254
255 pr_end_line ( (double) begin_x, (double) last_p->u.bar_p->c[AX],
256 (double) y, carryout);
257}
258\f
259
260/* print label at the beginning of an ending, along with the vertical line
261 * to the left of the label */
262
263static void
264pr_endlabel(x, y, label)
265
266double x; /* coordinate of beginning of ending */
267double y;
268char *label; /* the ending label or NULL */
269
270{
271 /* if there is a label, this is the beginning of an ending, so
272 * print a vertical line followed by the label */
273 if (label != (char *) 0) {
274 x += (2.0 * STDPAD);
275 draw_line(x, y + STDPAD, x, y + ENDINGHEIGHT - STDPAD);
276 pr_string(x + (3.0 * STDPAD), y + (2.0 * STDPAD), label, J_LEFT,
277 (char *) 0, -1);
278 }
279}
280\f
281
282/* print horizontal line above ending, possibly with ending vertical line */
283
284static void
285pr_end_line(begin_x, end_x, y, carryout)
286
287double begin_x; /* horizontal coordinates of ending line */
288double end_x;
289double y; /* vertical position */
290int carryout; /* if YES, continued on next score, so no end vertical */
291
292{
293 /* adjust to allow a little padding */
294 begin_x += (2.0 * STDPAD);
295 end_x -= (2.0 * STDPAD);
296 y += ENDINGHEIGHT - STDPAD;
297
298 /* draw the horizontal line above the ending */
299 draw_line(begin_x, y, end_x, y);
300
301 /* if the ending ends here, draw vertical line at end */
302 if (carryout == NO) {
303 draw_line(end_x, y, end_x, y - ENDINGHEIGHT + (2.0 * STDPAD));
304 }
305}
306\f
307
308/* function to tell whether a given staff should have ending put on it. Returns
309 * YES if it does, NO if it doesn't */
310
311int
312has_ending(staffno)
313
314int staffno; /* which staff to check */
315
316{
317 register int s; /* index through barstlist */
318
319
320 /* if staff is invisible, ending doesn't count */
321 if ( svpath(staffno, VISIBLE)->visible == NO) {
322 return(NO);
323 }
324
325 switch ( Score.endingstyle ) {
326
327 case ENDING_TOP:
328 /* if there is an earlier staff that is visible, then no
329 * ending on this one. Otherwise there is */
330 return( is_top_visible_in_range(staffno, 1) );
331
332 case ENDING_BARRED:
333 /* go through barstlist. If this
334 * staff is within a range and any staffs above it are
335 * invisible at the moment, it gets an ending. */
336 for (s = 0; s < Score.nbarst; s++) {
337
338 if ((staffno >= Score.barstlist[s].top) && (staffno
339 <= Score.barstlist[s].bottom)) {
340
341 return( is_top_visible_in_range(staffno,
342 Score.barstlist[s].top));
343 }
344 }
345
346 /* if wasn't in any of the ranges, then it must be barred
347 * by itself */
348 return(YES);
349
350 case ENDING_GROUPED:
351 /* go through brace and bracket list. If the top visible
352 * of any of them match the given score, it gets an ending */
353 for (s = 0; s < Score.nbrace; s++) {
354
355 if ((staffno >= Score.bracelist[s].topstaff)
356 && (staffno
357 <= Score.bracelist[s].botstaff)) {
358
359 return( is_top_visible_in_range(staffno,
360 Score.bracelist[s].topstaff));
361 }
362 }
363
364 for (s = 0; s < Score.nbrack; s++) {
365
366 if ((staffno >= Score.bracklist[s].topstaff)
367 && (staffno
368 <= Score.bracklist[s].botstaff)) {
369
370 return( is_top_visible_in_range(staffno,
371 Score.bracklist[s].topstaff));
372 }
373 }
374
375 /* wasn't in either list, so it probably shouldn't have an
376 * ending. However, if it happens to be the top staff, we
377 * better put one on anyway, because the top staff should
378 * always get an ending. */
379 return( is_top_visible_in_range(staffno, 1) );
380
381 default:
382 pfatal("unknown endingstyle");
383 }
384 /*NOTREACHED*/
385 return(NO);
386}
387\f
388
389/* given a staff number and the top of a range of staffs, return YES if the
390 * given staff is the top visible staff in the range, otherwise return NO.
391 * Assume that staffno itself is for a visible staff */
392
393int
394is_top_visible_in_range(staffno, top)
395
396int staffno; /* which staff to check */
397int top; /* top of range to check */
398
399{
400 for (staffno--; staffno >= top; staffno--) {
401 if ( svpath(staffno, VISIBLE)->visible == YES) {
402 /* something above it is visible */
403 return(NO);
404 }
405 }
406 return(YES);
407}
408\f
409
410/* functions for printing piano pedals marks */
411
412/* keep track of where last coordinate of pedal mark was for each staff.
413 * If no pedal currently on a staff, set to 0.0 */
414static float Last_ped_x[MAXSTAFFS + 1];
415static float Last_ped_y[MAXSTAFFS + 1];
416
417
418/* return the distance to offset the 'P' of "Ped." from the group X to
419 * center it on the group. The first time called, calculate the width,
420 * after that, just return it */
421
422double
423ped_offset()
424
425{
426 static double width = 0.0;
427 char pstr[4];
428
429 if (width == 0.0) {
430 /* first time, make string with just P and get width of that */
431 (void) strncpy(pstr, Ped_start, 3);
432 pstr[3] = '\0';
433 width = strwidth(pstr) / 2.0;
434 }
435 return(width);
436}
437\f
438
439/* when we encounter a ST_PEDAL, print the pedal character and save the
440 * east boundary as the last pedal x value, for later use. If is endped,
441 * set this last pedal x value to 0.0 */
442
443void
444pr_ped_char(stuff_p, staffno)
445
446struct STUFF *stuff_p; /* pedal info */
447int staffno; /* which staff */
448
449{
450 int font;
451 int size;
452 char *string;
453 int pedstyle; /* P_PEDSTAR or P_ALTPEDSTAR or P_LINE */
454 int pedchar; /* pedal music character code */
455 double overlap; /* to avoid tiny gaps in pedal line due to roundoff */
456 char *adj_pstart; /* Ped_start adjusted for Staffscale */
457 char *adj_pstop; /* Ped_stop adjusted for Staffscale */
458
459
460 if (stuff_p->string == (char *) 0) {
461 /* must be a pedal mark carried over from a previous
462 * score. Just need to save away the coordinate. */
463 Last_ped_x[staffno] = stuff_p->c[AX];
464 Last_ped_y[staffno] = stuff_p->c[AY];
465 return;
466 }
467
468 pedstyle = svpath(staffno, PEDSTYLE)->pedstyle;
469
470 /* extract the pedal character to be printed */
471 font = stuff_p->string[0];
472 size = stuff_p->string[1];
473 string = stuff_p->string + 2;
474 pedchar = next_str_char(&string, &font, &size) & 0xff;
475
476 /* overlap lines just slightly with pedal characters, to compensate
477 * for any rounding of the bounding box which might cause a tiny
478 * gap to appear between the line and the pedal character */
479 overlap = Stdpad / 3.0;
480
481 /* draw line from previous pedal character, if any, to this one */
482 if (pedstyle == P_LINE && Last_ped_x[staffno] != 0.0) {
483 if (stuff_p->c[AW] + overlap - Last_ped_x[staffno] > 0) {
484 do_linetype(L_NORMAL);
485 draw_line(Last_ped_x[staffno], stuff_p->c[AY],
486 stuff_p->c[AW] + overlap, stuff_p->c[AY]);
487 }
488 }
489
490 Last_ped_y[staffno] = stuff_p->c[AY];
491
492 switch (pedchar) {
493
494 case C_BEGPED:
495 if (Last_ped_x[staffno] != 0.0) {
496 /* This used to be a pfatal, because it should
497 * never happen. But it can happen
498 * due to user error: if user does something like
499 * pedal ....
500 * repeatend
501 * ....
502 * pedal ...
503 * repeatend
504 * Having two repeatends without an intervening
505 * repeatstart is illegal.
506 * It has never shown up as a true pfatal in millions
507 * of test runs, so if it is ever hit,
508 * it's probably the user error case.
509 */
510 ufatal("got begin pedal when already doing pedal, staff %d", staffno);
511 }
512 Last_ped_x[staffno] = stuff_p->c[AE] - overlap;
513 break;
514
515 case C_PEDAL:
516 if (Last_ped_x[staffno] == 0.0) {
517 pfatal("got pedal without begped, staff %d", staffno);
518 }
519 Last_ped_x[staffno] = stuff_p->c[AE] - overlap;
520 break;
521
522 case C_ENDPED:
523 if (Last_ped_x[staffno] == 0.0) {
524 pfatal("got endped without begped, staff %d", staffno);
525 }
526 Last_ped_x[staffno] = 0.0;
527 break;
528
529 default:
530 pfatal("bad character 0x%x in pedal string", pedchar);
531 /*NOTREACHED*/
532 break;
533 }
534
535 /* now print the appropriate pedal character */
536 if (pedstyle == P_LINE) {
537 /* We used to print the pedal characters from FONT_MUSIC,
538 * but Ghostscript sometimes misaligned them with the
539 * pedal lines, so now we draw the characters "manually."
540 */
541 do_linetype(L_NORMAL);
542 switch (pedchar) {
543 case C_BEGPED:
544 draw_line(stuff_p->c[AX], stuff_p->c[AN],
545 stuff_p->c[AX], stuff_p->c[AY]);
546 draw_line(stuff_p->c[AX], stuff_p->c[AY],
547 stuff_p->c[AE] + overlap, stuff_p->c[AY]);
548 break;
549 case C_PEDAL:
550 draw_line(stuff_p->c[AW], stuff_p->c[AY],
551 stuff_p->c[AX], stuff_p->c[AN]);
552 draw_line(stuff_p->c[AX], stuff_p->c[AN],
553 stuff_p->c[AE], stuff_p->c[AY]);
554 break;
555 case C_ENDPED:
556 draw_line(stuff_p->c[AW] - overlap, stuff_p->c[AY],
557 stuff_p->c[AX], stuff_p->c[AY]);
558 draw_line(stuff_p->c[AX], stuff_p->c[AN],
559 stuff_p->c[AX], stuff_p->c[AY]);
560 break;
561
562 }
563
564 }
565 else {
566 /* If we need to adjust for Staffscale, make a temp copy */
567 if (Staffscale != 1.0) {
568 adj_pstart = copy_string(Ped_start + 2,
569 (int) Ped_start[0],
570 adj_size( (int) Ped_start[1], Staffscale,
571 (char *) 0, -1));
572 adj_pstop = copy_string(Ped_stop + 2,
573 (int) Ped_stop[0],
574 adj_size( (int) Ped_stop[1], Staffscale,
575 (char *) 0, -1));
576 }
577 else {
578 adj_pstart = Ped_start;
579 adj_pstop = Ped_stop;
580 }
581
582 /* In alt pedstar style, a PEDAL is treated exactly like
583 * a BEGPED, so pretend that's what we got. */
584 if (pedstyle == P_ALTPEDSTAR && pedchar == C_PEDAL) {
585 pedchar = C_BEGPED;
586 }
587
588 switch (pedchar) {
589
590 case C_BEGPED:
591 pr_string(stuff_p->c[AX] - (strwidth(adj_pstart) / 2.0),
592 stuff_p->c[AY], adj_pstart,
593 J_CENTER, stuff_p->inputfile,
594 stuff_p->inputlineno);
595 break;
596
597 case C_PEDAL:
598 pr_string(stuff_p->c[AX] - strwidth(adj_pstop)
599 - ped_offset() * Staffscale,
600 stuff_p->c[AY], adj_pstop,
601 J_RIGHT, stuff_p->inputfile,
602 stuff_p->inputlineno);
603 pr_string(stuff_p->c[AX] - ped_offset() * Staffscale,
604 stuff_p->c[AY], adj_pstart,
605 J_LEFT, stuff_p->inputfile,
606 stuff_p->inputlineno);
607 break;
608
609 case C_ENDPED:
610 pr_string(stuff_p->c[AX] - (strwidth(adj_pstop) / 2.0),
611 stuff_p->c[AY], adj_pstop,
612 J_CENTER, stuff_p->inputfile,
613 stuff_p->inputlineno);
614 break;
615
616 default:
617 pfatal("bad character 0x%x in pedal string", pedchar);
618 /*NOTREACHED*/
619 break;
620 }
621
622 /* If we had to make a temp copy to account for Staffscale,
623 * free the temp copy. */
624 if (Staffscale != 1.0) {
625 FREE(adj_pstart);
626 FREE(adj_pstop);
627 }
628 }
629}
630\f
631
632/* when we hit a bar line, extend any pedal marks to the bar line. Since things
633 * are stored in units of bars, easier to do this than keep track of the
634 * entire length and have to worry about page feeds, etc. This is just for
635 * normal bars, not pseudo-bars. They are handled separately. */
636
637void
638pr_ped_bar(mll_p, bar_p)
639
640struct MAINLL *mll_p; /* print pedal mark up to bar hangs off of here */
641struct BAR *bar_p; /* print pedal marks up to this bar */
642
643{
644 register int s;
645 float endadj; /* adjustment for endings */
646
647
648 /* for each staff that has pedal marks pending, draw an extension
649 * line to where this bar line is and reset Last_ped_x */
650 for (s = 1; s <= Score.staffs; s++) {
651
652 if (Last_ped_x[s] != 0.0) {
653
654 if (Last_ped_y[s] <= 0.0) {
655 pfatal("don't have y coordinate for drawing pedal mark");
656 }
657
658 endadj = 0.0;
659 if (svpath(s, PEDSTYLE)->pedstyle == P_LINE) {
660 do_linetype(L_NORMAL);
661 if (Ped_snapshot[0] == YES &&
662 bar_p->endingloc == STARTITEM) {
663
664 /* going into 2nd ending, so shorten
665 * this pedal to not reach bar */
666 endadj = (2.0 * STEPSIZE);
667 /* if line length is positive,
668 * draw it */
669 if (bar_p->c[AX] - endadj
670 > Last_ped_x[s]) {
671
672 draw_line(Last_ped_x[s],
673 Last_ped_y[s],
674 bar_p->c[AX] - endadj,
675 Last_ped_y[s]);
676 }
677 }
678 else {
679 if (bar_p->c[AX] - STDPAD >
680 Last_ped_x[s]) {
681 draw_line(Last_ped_x[s],
682 Last_ped_y[s],
683 bar_p->c[AX]
684 - STDPAD,
685 Last_ped_y[s]);
686 endadj = -STDPAD;
687 }
688 else {
689 endadj = -(bar_p->c[AX]
690 - Last_ped_x[s]);
691 }
692 }
693 }
694
695 Last_ped_x[s] = bar_p->c[AX] + endadj;
696 }
697 }
698 saveped(mll_p, bar_p);
699}
700\f
701
702/* handle pedal going into endings. When we hit a first ending, save the
703 * state of the pedal for all staffs. On subsequent endings in the set,
704 * reset the pedal state to what it was at the beginning of the first ending.
705 * At the endending, go back to normal operation. */
706
707void
708saveped(mll_p, bar_p)
709
710struct MAINLL *mll_p; /* bar is connected here */
711struct BAR *bar_p;
712
713{
714 register int s; /* staff index */
715
716
717 if (mll_p == (struct MAINLL *) 0) {
718 pfatal("null pointer in saveped");
719 }
720
721 if (bar_p->endingloc == STARTITEM) {
722
723 if (Ped_snapshot[0] == YES) {
724
725 /* starting 2nd ending: restore pedal state as it was
726 * at beginning of first ending */
727 for (s = 1; s <= Score.staffs; s++) {
728 if (Ped_snapshot[s] == YES) {
729 Last_ped_x[s] = bar_p->c[AX]
730 + (2.0 * STEPSIZE);
731 }
732 else {
733 Last_ped_x[s] = 0.0;
734 }
735 }
736 }
737
738 else {
739 /* starting a set of endings,
740 * need to save pedal state at this
741 * point so we can carry it into subsequent endings */
742 for (s = 1; s <= Score.staffs; s++) {
743 /* set to YES if pedal is on */
744 Ped_snapshot[s] = (Last_ped_x[s] == 0.0 ? NO : YES);
745 }
746 /* make sure any remaining staffs are set to pedal off,
747 * in case user increases the number of staffs
748 * during the endings... */
749 for ( ; s <= MAXSTAFFS; s++) {
750 Ped_snapshot[s] = NO;
751 }
752
753 /* mark that we now have a snapshot */
754 Ped_snapshot[0] = YES;
755 }
756 }
757
758 else if (bar_p->endingloc == ENDITEM) {
759 /* at end of endings, discard snapshot of pedal states.
760 * However, we have to make sure this is really the end of
761 * endings, and not just a bar that was marked as end
762 * because the start of the next was moved from here to
763 * the pseudo bar. So we search forward, if we find a
764 * clefsig with pseudo-bar before finding a chhead,
765 * and that pseudo bar endingloc is STARTITEM, then this
766 * isn't really the end of endings, and should be ignored. */
767 for ( ; mll_p != (struct MAINLL *) 0; mll_p = mll_p->next) {
768 if (mll_p->str == S_CHHEAD) {
769 /* is end of endings */
770 break;
771 }
772 else if (mll_p->str == S_CLEFSIG &&
773 mll_p->u.clefsig_p->bar_p !=
774 (struct BAR *) 0 &&
775 mll_p->u.clefsig_p->bar_p->endingloc
776 == STARTITEM) {
777 /* not really end of endings */
778 return;
779 }
780 }
781 Ped_snapshot[0] = NO;
782 }
783}
784\f
785
786/* given a list of phrase mark curve coordinates, print the curve */
787/* output each x,y, coordinate pair, then the number of coordinates and
788 * finally the "curve" function name */
789
790void
791pr_phrase(crvlist_p, linetype, tapered, staffno)
792
793struct CRVLIST *crvlist_p; /* the curve to print */
794int linetype; /* if not tapered, may be L_DOTTED or L_DASHED*/
795int tapered; /* YES or NO */
796int staffno; /* which staff, to get staffscale */
797
798{
799 int n;
800 struct CRVLIST *c_p;
801 float *xlist,* ylist;
802
803 /* count up number of coordinates */
804 for (n = 0, c_p = crvlist_p; c_p != (struct CRVLIST *) 0;
805 c_p = c_p->next) {
806 n++;
807 }
808
809 MALLOCA(float, xlist, n);
810 MALLOCA(float, ylist, n);
811 for (n = 0, c_p = crvlist_p; c_p != (struct CRVLIST *) 0;
812 c_p = c_p->next, n++) {
813 xlist[n] = c_p->x;
814 ylist[n] = c_p->y;
815 }
816 if (tapered == NO) {
817 do_linetype(linetype);
818 }
819 pr_allcurve(xlist, ylist, n,
820 svpath(staffno, STAFFSCALE)->staffscale * W_MEDIUM / PPI,
821 tapered);
822 FREE(xlist);
823 FREE(ylist);
824}
825\f
826/*
827 * Name: pr_allcurve()
828 *
829 * Abstract: Print a curve, either generated (e.g., tie) or user-defined.
830 *
831 * Returns: void
832 *
833 * Description: This function is given an array of CURVEINFOs, one for each
834 * point of a curve, where x and y have been filled in. It fills
835 * the rest of the items in the structures, and prints PostScript
836 * commands for drawing the curve. If the curve is to be dashed
837 * or dotted, the calling function must put out the PostScript
838 * commands for that. It does not handle "wavy".
839 */
840
841
842void
843pr_allcurve(x, y, num, cwid, tapered)
844
845float x[], y[]; /* coordinates of the curve's points */
846int num; /* number of elements (points) in the array */
847double cwid; /* (max) width of the curve, in inches */
848int tapered; /* YES or NO */
849
850{
851 struct CURVEINFO *v; /* malloc structs for holding point info */
852 float *slen; /* malloc length of each segment */
853 float *xoff; /* malloc x offset of curve boundary from mid*/
854 float *yoff; /* malloc y offset of curve boundary from mid*/
855 float *off; /* malloc total offset of curve boundary */
856 float totlen; /* len of curve, along segments */
857 float maxplace; /* distance from end where a tapered curve */
858 /* reaches its maximum thickness */
859 float cumlen, remlen, fromend; /* used in tapering curve */
860 float dx, dy; /* for finding x and y offsets */
861 float temp; /* temp variable */
862 int n; /* loop through points */
863
864
865 /*
866 * If the curve is not to be tapered, calculate Bezier curves joining
867 * these points, and stroke the resulting path.
868 */
869 if (tapered == NO) {
870 /* load coords into structures, and calculate control points */
871 MALLOC(CURVEINFO, v, num);
872 for (n = 0; n < num; n++) {
873 v[n].x = x[n];
874 v[n].y = y[n];
875 }
876 calccurve(v, num);
877
878 /* output results in PostScript */
879 do_moveto(v[0].x, v[0].y);
880
881 for (n = 0; n < num - 1; n++) {
882 do_curveto( v[n].x1, v[n].y1, v[n].x2, v[n].y2,
883 v[n+1].x, v[n+1].y);
884 }
885 do_stroke();
886
887 FREE(v);
888 return;
889 }
890
891 /*
892 * The curve is to be tapered. We're going to draw two series of
893 * Bezier curves, forming the boundaries of the whole curve, and then
894 * fill. Note that this will always result in a solid curve,
895 * regardless of any earlier request for dashes or dots.
896 */
897 /* first allocate the arrays we're going to need */
898 MALLOC(CURVEINFO, v, num);
899 MALLOCA(float, slen, num);
900 MALLOCA(float, xoff, num);
901 MALLOCA(float, yoff, num);
902 MALLOCA(float, off, num);
903
904 /* find and save len of each segment, and accumulate total len */
905 totlen = 0;
906 for (n = 0; n < num - 1; n++) {
907 slen[n] = sqrt( (double) (SQUARED(x[n+1] - x[n]) +
908 SQUARED(y[n+1] - y[n]) ) );
909 totlen += slen[n];
910 }
911
912 /*
913 * Tapering occurs up to a max of 1/3 inches from the end of a curve.
914 * maxplace is set up such that it is normally the distance from the
915 * end where the max thickness is attained. However, if a curve is
916 * shorter than 2/3 inches, it will never attain this max thickness.
917 */
918 if (totlen > 0.5) {
919 maxplace = 1.0/3.0;
920 } else {
921 maxplace = totlen * (2.0/3.0);
922 }
923
924 cumlen = 0.0; /* none accumulated so far */
925 remlen = totlen; /* all of it remains */
926 for (n = 0; n < num; n++) {
927 /* whichever end this point is closer to, note distance */
928 if (cumlen < remlen) {
929 fromend = cumlen;
930 }
931 else {
932 fromend = remlen;
933 }
934
935 /* set the offset for this point for achieving tapering */
936 if (fromend > maxplace) {
937 off[n] = cwid / 2.0;
938 } else {
939 float taperwid;
940 /*
941 * For curves longer than 2/3, taperwid should be only
942 * half of TAPERWID; for zero length curves, it should
943 * be full TAPERWID; in between, adjust linearly. Then,
944 * at the ends, the width is taperwid times the full
945 * standard thickness (the middle of a long curve); and
946 * ramp up linearly towards 1/3 inches from the end.
947 */
948 taperwid = totlen > 2.0/3.0 ? TAPERWID / 2.0 :
949 TAPERWID * (1.0 - 0.75 * totlen);
950 off[n] = (cwid / 2.0) *
951 ((1.0 - taperwid) * fromend / maxplace + taperwid);
952 }
953
954 /*
955 * Break offset into x and y components, based on the slope
956 * between the two surrounding points. For the endpoints,
957 * there are not two surrounding points, so use the slope of
958 * the neighboring segment.
959 */
960 /*
961 * First get deltas; x and y are switched and sign reversed
962 * on one, because we're concerned with the line perpendicular
963 * to the line joining the two points. (Its slope is the
964 * negative inverse.) Only the ratio dx/dy matters, not the
965 * values.
966 */
967 if (n == 0) {
968 dx = y[1] - y[0];
969 dy = x[0] - x[1];
970 } else if (n == num - 1) {
971 dx = y[num-1] - y[num-2];
972 dy = x[num-2] - x[num-1];
973 } else {
974 dx = y[n+1] - y[n-1];
975 dy = x[n-1] - x[n+1];
976 }
977
978 /* get hypotenuse of something */
979 temp = off[n] / sqrt( (double) (SQUARED(dx) + SQUARED(dy)) );
980
981 /* get x and y offsets; may need to switch signs */
982 xoff[n] = fabs(temp * dx);
983 yoff[n] = fabs(temp * dy);
984 if (dx > 0)
985 xoff[n] *= -1;
986 if (dy > 0)
987 yoff[n] *= -1;
988
989 /* update cumulative and remaining length if not at end */
990 if (n < num - 1) {
991 cumlen += slen[n];
992 remlen -= slen[n];
993 }
994 }
995
996 /*
997 * Load coords into structures, and calculate control points, for one
998 * boundary of the curve.
999 */
1000 for (n = 0; n < num; n++) {
1001 v[n].x = x[n] + xoff[n];
1002 v[n].y = y[n] + yoff[n];
1003 }
1004 calccurve(v, num);
1005
1006 /*
1007 * Move to the center of the curve's thickness at the beginning point.
1008 * Draw a perpendicular line to the curve's boundary. Then generate
1009 * the curves that form this side's boundary.
1010 */
1011 do_moveto(x[0], y[0]);
1012 do_line(v[0].x, v[0].y);
1013 for (n = 0; n < num - 1; n++) {
1014 do_curveto( v[n].x1, v[n].y1, v[n].x2, v[n].y2,
1015 v[n+1].x, v[n+1].y);
1016 }
1017
1018 /*
1019 * Load coords into structures, and calculate control points, for the
1020 * other boundary of the curve. We're going to do this side in
1021 * reverse, back to the beginning.
1022 */
1023 for (n = 0; n < num; n++) {
1024 v[n].x = x[num - 1 - n] - xoff[num - 1 - n];
1025 v[n].y = y[num - 1 - n] - yoff[num - 1 - n];
1026 }
1027 calccurve(v, num);
1028
1029 /*
1030 * Draw a line across the curve's thickness at this end. Then
1031 * generate the curves that form this side's boundary.
1032 */
1033 do_line( v[0].x, v[0].y);
1034 for (n = 0; n < num - 1; n++) {
1035 do_curveto( v[n].x1, v[n].y1, v[n].x2, v[n].y2,
1036 v[n+1].x, v[n+1].y);
1037 }
1038
1039 /* fill to form the full, solid curve */
1040 do_fill();
1041
1042 FREE(slen);
1043 FREE(xoff);
1044 FREE(yoff);
1045 FREE(off);
1046 FREE(v);
1047}
1048\f
1049/*
1050 * Name: calccurve()
1051 *
1052 * Abstract: Calculate info for drawing cubic arcs through a curve's points.
1053 *
1054 * Returns: void
1055 *
1056 * Description: This function is given an array of CURVEINFOs, one for each
1057 * point of a curve, where x and y have been filled in. It fills
1058 * in the rest of the items in the structures. Specifically, in
1059 * each structure, (x1, y1) and (x2, y2) are the control points
1060 * for drawing a Bezier curve (using curveto) from this point to
1061 * the next one. These points are chosen in such a way that the
1062 * slopes at the end of one curve and the start of the next are
1063 * equal, to avoid a sharp corner. Also, the angles this slope
1064 * forms with straight line segments connecting the points are
1065 * equal. The angle at the start of the first curve is set equal
1066 * to the angle at the end of it, and the angle at the end of the
1067 * last curve is set equal to the angle at the beginning of it.
1068 * The other fields in the CURVEINFOs are set but aren't useful to
1069 * the caller. In the last point's CURVEINFO, some of the fields
1070 * are not used (including x1, y1, x2, y2).
1071 */
1072
1073
1074static void
1075calccurve(v, num)
1076
1077struct CURVEINFO v[]; /* array of curve points, x and y must be filled in */
1078int num; /* number of elements (points) in the array */
1079
1080{
1081 int n; /* loop through the points */
1082 float temp, delx, dely; /* temp variables */
1083 float slope, intercept; /* for equation of a segment */
1084
1085
1086 /* find the length of each segment connecting neighboring points */
1087 for (n = 0; n < num - 1; n++) {
1088 /* use Pythagorean theorem; put result in 1st point's "len" */
1089 delx = v[n+1].x - v[n].x;
1090 dely = v[n+1].y - v[n].y;
1091 v[n].len = sqrt( (double) (SQUARED(delx) + SQUARED(dely)) );
1092 if (v[n].len == 0)
1093 ufatal("two curve points are equal");
1094 }
1095
1096 /* find the angle at each point other than the endpoints */
1097 for (n = 1; n < num - 1; n++) {
1098 /*
1099 * Use the law of cosines on the triangle formed by this point
1100 * and the preceding and following points. First get the delta
1101 * from the preceding point to the following point.
1102 */
1103 delx = v[n+1].x - v[n-1].x;
1104 dely = v[n+1].y - v[n-1].y;
1105
1106 /*
1107 * The law of cosines: c^2 = a^2 + b^2 - 2 a b cos(C).
1108 * Solve this for the cosine of our point's angle (angle "C").
1109 */
1110 temp = ( SQUARED(v[n-1].len) + SQUARED(v[n].len) -
1111 (SQUARED(delx) + SQUARED(dely)) ) /
1112 ( 2.0 * v[n-1].len * v[n].len );
1113
1114 /* if angle is 180, should be -1, but guard against roundoff */
1115 if (temp < -1)
1116 temp = -1; /* should have been exactly -1 */
1117
1118 /* if angle is 0, this is not allowed in our curve */
1119 if (temp >= 1)
1120 ufatal("curve bends all the way back on itself");
1121
1122 v[n].ang = acos(temp);
1123 }
1124
1125 /* set the bend direction at each point other than the endpoints */
1126 for (n = 1; n < num - 1; n++) {
1127 /* handle special case where previous segment is vertical */
1128 if (v[n-1].x == v[n].x) {
1129 if (v[n-1].y < v[n].y) {
1130 if (v[n+1].x >= v[n].x)
1131 v[n].bend = 1;
1132 else
1133 v[n].bend = -1;
1134 } else {
1135 if (v[n+1].x >= v[n].x)
1136 v[n].bend = -1;
1137 else
1138 v[n].bend = 1;
1139 }
1140 continue; /* go to next loop iteration */
1141 }
1142
1143 /*
1144 * Find the equation of the previous segment. Plug the
1145 * following point's x into that equation to get where its y
1146 * would have been if the angle were 180. Comparing that y to
1147 * the actual y, we can determine the bend direction.
1148 */
1149 slope = (v[n].y - v[n-1].y) / (v[n].x - v[n-1].x);
1150 intercept = v[n-1].y - slope * v[n-1].x;
1151 temp = slope * v[n+1].x + intercept;
1152
1153 if (v[n].x > v[n-1].x) {
1154 if (v[n+1].y < temp)
1155 v[n].bend = 1;
1156 else
1157 v[n].bend = -1;
1158 } else {
1159 if (v[n+1].y < temp)
1160 v[n].bend = -1;
1161 else
1162 v[n].bend = 1;
1163 }
1164 }
1165
1166 /*
1167 * At the endpoints, there is only one segment, so no angle or bend
1168 * direction is defined. But we need to have something. So we semi-
1169 * arbitrarily set these to the same value as their neighboring points.
1170 */
1171 v[0].ang = v[1].ang;
1172 v[0].bend = v[1].bend;
1173 v[num-1].ang = v[num-2].ang;
1174 v[num-1].bend = v[num-2].bend;
1175
1176 /*
1177 * For all points, set the slope of the line tangent to the curves
1178 * we're going to draw, in the coordinate system where the segment
1179 * starting at this point is horizontal. (This is the coordinate
1180 * system that findcontrol() uses.) Since the angle between segments
1181 * is not allowed to be 0, this slope is never vertical (infinity).
1182 */
1183 for (n = 0; n < num; n++)
1184 v[n].slopetan = -v[n].bend * tan( v[n].ang / 2 + PI / 2);
1185
1186 /*
1187 * For each segment, calculate control points to define a Bezier curve
1188 * connecting the endpoints, according to the specifications.
1189 */
1190 for (n = 0; n < num - 1; n++)
1191 findcontrol(v, n);
1192}
1193\f
1194/*
1195 * Name: findcontrol()
1196 *
1197 * Abstract: Find Bezier control points for one segment of the curve.
1198 *
1199 * Returns: void
1200 *
1201 * Description: This function is given an array of CURVEINFOs, one for each
1202 * point, with everything filled in except the control points. It
1203 * calculates them and fills them in.
1204 */
1205
1206
1207static void
1208findcontrol(v, n)
1209
1210struct CURVEINFO v[];
1211int n;
1212
1213{
1214 float costheta, sintheta; /* for rotating axes by theta */
1215
1216 /*
1217 * All of the following variables refer to the rotated/translated
1218 * position of the segment (see comment below). Point 0 is the
1219 * starting point, point 3 is the ending point, and points 1 and 2
1220 * are the Bezier control points.
1221 */
1222 float x1, x2, y1, y2; /* control points */
1223 float x3; /* end point (y3 is always 0) */
1224 float slope0, slope3; /* slope of tangent lines at endpoints */
1225 float b, c; /* some coefficients of cubic y = f(x) */
1226 float cx, by, cy; /* Bezier coefficients */
1227
1228
1229 /*
1230 * Rotate and translate the axes so that the starting point (point 0)
1231 * is at the origin, and the ending point (3) is on the positive
1232 * x axis. Their coords are (0, 0) and (v[n].len, 0). We are going
1233 * to find a cubic equation that intersects the endpoints, and has the
1234 * necessary slope at those points such that the tangent line's slope
1235 * is halfway between horizontal (this segment) and the slope of the
1236 * neighboring segment. The equation is
1237 * y = a x^3 + b x^2 + c x + d
1238 * so the slope is
1239 * y' = 3 a x^2 + 2 b x + c
1240 * By plugging the two points into these, you get 4 equations in the 4
1241 * unknowns a, b, c, d.
1242 */
1243 x3 = v[n].len;
1244
1245 /* find the slope of the tangent lines at the first & second points */
1246 slope0 = v[n].slopetan;
1247 slope3 = -v[n+1].slopetan;
1248
1249 /* set values of a, b, c (d turns out to be always 0) */
1250 /* a = (slope0 + slope3) / SQUARED(x3); don't really need this one */
1251 b = (-2 * slope0 - slope3) / x3;
1252 c = slope0;
1253
1254 /*
1255 * For Bezier version of this, let x = t / x3, and for y, plug this
1256 * into the cubic we have found. This gives us the Bezier coeff.:
1257 * x = ax t^3 + bx t^2 + cx t + x0
1258 * y = ay t^3 + by t^2 + cy t + y0
1259 */
1260 /* ax and bx are always 0 */
1261 cx = x3;
1262 /* ay = a * CUBED(x3); this one is not needed */
1263 by = b * SQUARED(x3);
1264 cy = c * x3;
1265
1266 /* get control points 1 and 2 from Bezier coefficients & endpoints */
1267 x1 = cx / 3;
1268 y1 = cy / 3;
1269 x2 = x1 + cx / 3;
1270 y2 = y1 + (by + cy) / 3;
1271
1272 /*
1273 * Rotate and translate the axes back to where they really were. Store
1274 * these real positions of the control points.
1275 */
1276 costheta = (v[n+1].x - v[n].x) / v[n].len;
1277 sintheta = (v[n+1].y - v[n].y) / v[n].len;
1278
1279 v[n].x1 = v[n].x + x1 * costheta - y1 * sintheta;
1280 v[n].x2 = v[n].x + x2 * costheta - y2 * sintheta;
1281 v[n].y1 = v[n].y + y1 * costheta + x1 * sintheta;
1282 v[n].y2 = v[n].y + y2 * costheta + x2 * sintheta;
1283}
1284\f
1285
1286/* draw a V-shaped bend indicator by drawing two line segments */
1287
1288void
1289pr_bend(crvlist_p)
1290
1291struct CRVLIST *crvlist_p;
1292
1293{
1294 if (crvlist_p == (struct CRVLIST *) 0
1295 || crvlist_p->next == (struct CRVLIST *) 0
1296 || crvlist_p->next->next == (struct CRVLIST *) 0) {
1297 pfatal("invalid bend crvlist");
1298 }
1299
1300 do_linetype(L_NORMAL);
1301 draw_line(crvlist_p->x, crvlist_p->y, crvlist_p->next->x, crvlist_p->next->y);
1302 draw_line(crvlist_p->next->x, crvlist_p->next->y,
1303 crvlist_p->next->next->x, crvlist_p->next->next->y);
1304}
1305\f
1306
1307/* draw a slide for a tab or tabnote staff. Slides are stored internally
1308 * like slurs. Here we just draw a line between the appropriate coordinates */
1309
1310void
1311pr_tabslur(crvlist_p, ts_style)
1312
1313struct CRVLIST *crvlist_p;
1314int ts_style;
1315
1316{
1317 if (crvlist_p == (struct CRVLIST *) 0
1318 || crvlist_p->next == (struct CRVLIST *) 0) {
1319 pfatal("invalid tabslur crvlist");
1320 }
1321
1322 do_linetype(ts_style);
1323 draw_line(crvlist_p->x, crvlist_p->y, crvlist_p->next->x, crvlist_p->next->y);
1324}
1325\f
1326
1327/* print a small curve to indicate a 1/4 step bend on a tabnote */
1328
1329void
1330pr_sm_bend(x, y)
1331
1332double x, y; /* where to start the curve. This is the bottom left end */
1333
1334{
1335 float xlist[4], ylist[4]; /* coordinates of the curve */
1336
1337
1338 /* fill in the relative horizontal and vertical offsets. These
1339 * are hand picked to give a nice looking curve */
1340 xlist[0] = x;
1341 ylist[0] = y;
1342 xlist[1] = x + 0.5 * STEPSIZE;
1343 ylist[1] = y + 0.2 * STEPSIZE;
1344 xlist[2] = x + 1.2 * STEPSIZE;
1345 ylist[2] = y + 1.2 * STEPSIZE;
1346 xlist[3] = x + 1.3 * STEPSIZE;
1347 ylist[3] = y + 1.75 * STEPSIZE;
1348
1349 /* now print the curve */
1350 pr_allcurve(xlist, ylist, 4, W_NORMAL, NO);
1351}
1352\f
1353
1354/* Print 'atend' grids */
1355
1356void
1357pr_atend()
1358
1359{
1360 float x; /* of first grid of row */
1361 float y; /* of row; top line of grid */
1362 float gridx; /* x of grid being printed */
1363 float north; /* of the grid */
1364 float space; /* distance between grid lines */
1365 struct GRID *grid_p;
1366 int g; /* index through grid_p array */
1367 int staff = -1; /* always -1 to indicate atend. Using a
1368 * variable rather than hard-coding where
1369 * used just on general principles. */
1370 int column; /* how many columns printed so far in row */
1371 int rows_to_print; /* how many rows to print per page. */
1372 struct MAINLL *main_feed_p; /* for getting top/bottom blocks */
1373 struct FEED *feed_p; /* for getting top/bottom blocks */
1374
1375
1376 x = Atend_info.firstgrid_x;
1377 y = Atend_info.firstgrid_y;
1378 rows_to_print = Atend_info.rows_per_page;
1379 space = gridspace(staff);
1380 column = 0;
1381
1382 /* Find the last FEED. We use that to get top/bottom blocks */
1383 for (main_feed_p = Mainlltc_p; main_feed_p->str != S_FEED;
1384 main_feed_p= main_feed_p->prev) {
1385 ;
1386 }
1387 feed_p = main_feed_p->u.feed_p;
1388 for (g = 0; g < Atend_info.grids_used; g++) {
1389 grid_p = Atend_info.grid_p[g];
1390 gridsize(grid_p, staff, &north, (float *) 0, (float *) 0, (float *) 0);
1391 /* calculate horizontal position of this grid */
1392 gridx = x + column * Atend_info.horz_sep;
1393
1394 /* print the name of the grid */
1395 pr_string(gridx - strwidth(grid_p->name) / 2.0,
1396 y + north + strdescent(grid_p->name),
1397 grid_p->name, J_LEFT, (char *) 0, -1);
1398
1399 /* print the grid itself */
1400 do_grid(gridx - space * (grid_p->numstr - 1) / 2.0,
1401 y, space, grid_p, staff);
1402
1403 if (++column >= Atend_info.grids_per_row &&
1404 g < Atend_info.grids_used - 1) {
1405 /* move to next row */
1406 column = 0;
1407 y -= Atend_info.vert_sep;
1408 rows_to_print--;
1409 if (Atend_info.separate_page == YES &&
1410 rows_to_print <= 0) {
1411 rows_to_print = Atend_info.rows_per_page;
1412 y = Atend_info.firstgrid_y;
1413 /* print top/bottom blocks, if any */
1414 /* use *2 blocks for any subsequent pages */
1415 feed_p->top_p = feed_p->top2_p;
1416 feed_p->bot_p = feed_p->bot2_p;
1417 pr_feed(main_feed_p);
1418 }
1419 }
1420 }
1421}