/************************************************* * The PMW Music Typesetter - 3rd incarnation * *************************************************/ /* Copyright (c) Philip Hazel, 1991 - 2020 */ /* Written by Philip Hazel, starting November 1991 */ /* This file last modified: December 2020 */ /* This file contains code for outputting things in PostScript */ #include "pmwhdr.h" #include "outhdr.h" #define UUSIZE 100 /* For list of unsupported Unicode codes */ /************************************************ * Static variables * ************************************************/ static BOOL ps_EPS = FALSE; static BOOL ps_slurA = FALSE; static int ps_caj = 0; static int ps_chcount; static int ps_curfont; static int ps_curfontsize; static BOOL ps_curfontX; static int ps_gray; static int ps_sheetwidth; static int ps_xmargin; static int ps_ymax; static uschar *ps_IdStrings[font_tablen+1]; static int ps_curfonttransform[6]; static int unsupported_unicode[UUSIZE]; static int uunext = 0; static int invalid_unicode[UUSIZE]; static int uinvnext = 0; static BOOL uuoverflow = FALSE; static BOOL uinvoverflow = FALSE; /************************************************ * Macros * ************************************************/ /* Coordinate translation for character output */ #define psxtran(x) ((x) + ps_xmargin) #define psytran(y) (ps_ymax - (y)) /************************************************* * Write to PS file, wrapping * *************************************************/ /* To keep the PostScript readable we wrap it mostly at 72 characters wide. Arguments: format a format string ... arguments for the format Returns: nothing */ static void ps_printf(const char *format, ...) { int len; uschar buff[256]; uschar *p = buff; va_list ap; va_start(ap, format); format_vsprintf(buff, format, ap); len = Ustrlen(buff); va_end(ap); if (ps_chcount > 0 && ps_chcount + len > 72) { fputc('\n', ps_file); ps_chcount = 0; } if (ps_chcount == 0 && *p == ' ') { p++; len--; } Ufputs(p, ps_file); if (p[len-1] == '\n') ps_chcount = 0; else ps_chcount += len; } /************************************************* * Check whether font needs changing * *************************************************/ /* The X argument is used when the character is > 255, indicating that we want the second version of the font, with the alternative encoding. Arguments: f font number s the font size X TRUE if it's the extended font we want Returns: TRUE if the font needs changing */ static BOOL ps_needchangefont(int f, int s, BOOL X) { int i; if (f != ps_curfont || X != ps_curfontX || s != ps_curfontsize) return TRUE; for (i = 0; i <= 5; i++) if (font_transform[i] != ps_curfonttransform[i]) return TRUE; return FALSE; } /************************************************* * Make a given font current * *************************************************/ /* This function is called when we know that a font change is needed. Arguments: f current font number s the font size X TRUE if it's the extended font we want Returns: nothing */ static void ps_setfont(int f, int s, BOOL X) { ps_curfont = f; ps_curfontX = X; ps_curfontsize = s; memcpy(ps_curfonttransform, font_transform, 6*sizeof(int)); /* Transformation is null: don't waste time/space by writing it */ if (font_transform[0] == 65536 && font_transform[1] == 0 && font_transform[2] == 0 && font_transform[3] == 65536 && font_transform[4] == 0 && font_transform[5] == 0) ps_printf(" %s%s %f ss%s", ps_IdStrings[f], X? "X" : "", s, main_righttoleft? "r" : ""); /* A genuine transform is set */ else ps_printf(" %s%s [%f %f %f %f %f %f] sm%s", ps_IdStrings[f], X? "X" : "", mac_muldiv(font_transform[0], s, 65536), mac_muldiv(font_transform[1], s, 65536), mac_muldiv(font_transform[2], s, 65536), mac_muldiv(font_transform[3], s, 65536), mac_muldiv(font_transform[4], s, 1000), mac_muldiv(font_transform[5], s, 1000), main_righttoleft? "r" : ""); } /************************************************* * End a text string * *************************************************/ /* This function writes the terminating ')' and an appropriate command. Arguments: absolute TRUE if the coordinates are absolute, else relative w extra space width x x-coordinate y y-coordinate */ static void ps_endstring(BOOL absolute, int w, int x, int y) { fputc(')', ps_file); /* Does not check ps_chcount */ ps_chcount++; if (absolute) { if (w != 0) ps_printf("%f %f %f ws", w, psxtran(x), psytran(y)); else ps_printf("%f %f s", psxtran(x), psytran(y)); } else if (x == 0 && y == 0) /* Relative, but no movement */ { if (w != 0) ps_printf("%f wsh", w); else ps_printf("sh"); } else { if (w != 0) ps_printf("%f %f %f wrs", w, x, y); else ps_printf("%f %f rs", x, y); } } /************************************************* * Basic string output code * *************************************************/ /* This function scans a string's characters and converts to the appropriate output encoding for the PostScript fonts. Arguments: s the string, in UTF-8 format f the font pointsize the font size absolute TRUE if the coordinates are absolute, else relative x the x coordinate y the y coordinate w extra width for spaces (for justifying) Returns: nothing */ static void ps_basic_string(uschar *s, int f, int pointsize, BOOL absolute, int x, int y, int w) { int font = font_table[f]; fontstr *fs = &(font_List[font]); kerntablestr *ktable = fs->kerns; BOOL instring = FALSE; BOOL stdencoding = fs->stdencoding; uschar *p, *endp; #ifdef SUPPORT_B2PF uschar b2pf_stack_buffer[B2PF_STACKBSIZE]; #endif /* If we have B2PF support, process this string if its font is set up for it. For the moment, just use the on-stack output buffer. If ever there's a complaint, we could retry the process using a larger heap buffer. */ #ifdef SUPPORT_B2PF if (font_b2pf_contexts[f] != NULL) { size_t ilen = Ustrlen(s); size_t used, eoffset; int rc; /* Reduce output size by 1 to allow for a terminating zero to be added. */ rc = b2pf_format_string(font_b2pf_contexts[f], (void *)s, ilen, (void *)b2pf_stack_buffer, B2PF_STACKBSIZE - 1, &used, font_b2pf_options[f], &eoffset); if (rc != B2PF_SUCCESS) { size_t buffused; char buffer[128]; (void)b2pf_get_error_message(rc, buffer, sizeof(buffer), &buffused, 0); buffer[buffused] = 0; error_moan(ERR156, s, buffer); /* Hard */ } b2pf_stack_buffer[used] = 0; s = b2pf_stack_buffer; } #endif /* Initialize start and end */ p = s; endp = s + Ustrlen(s); /* Check top/bottom of bbox for EPS. Allow 0.4*pointsize for descenders below and pointsize above. */ if (ps_EPS) { int descender = (4 * pointsize)/10; if (y + descender > out_bbox[1]) out_bbox[1] = y + descender; if (y - pointsize < out_bbox[3]) out_bbox[3] = y - pointsize; } /* When printing right-to-left, we need to find the complete length of the string so that we can print it from the other end, since the fonts still work left-to-right. We also need the length for EPS output, in order to set the bounding box. Finding the length requires a preliminary scan of the whole thing, somewhat repeating the printing scan below, but it was too messy to try to conflate the code. */ if (main_righttoleft || ps_EPS) { int swidth = 0; int last_width = 0; int last_r2ladjust = 0; for (;;) { int c; GETCHARINC(c, p); if (c == 0) break; if (!stdencoding) { c = font_utranslate(c, fs); if (c >= 256) { last_width = fs->widths[UNKNOWN_CHAR_N]; last_r2ladjust = fs->r2ladjusts[UNKNOWN_CHAR_N]; } else { int cj = c; /* Fudges for various special cases in the music font, where the adjustment bounding box is taken from another character. */ if (f == font_mf) { switch(c) { case 'J': /* Additional stem characters - adjust as for note */ case 'K': case 'o': case 'p': case 'q': case 'r': cj = '5'; break; case '7': /* Up quaver - adjust as for down quaver */ cj = '8'; break; case '9': /* Up semiquaver - adjust as for down semiquaver */ cj = ':'; break; default: break; } } last_width = fs->widths[c]; last_r2ladjust = fs->r2ladjusts[cj]; } } /* Standardly encoded font */ else { if (c < LOWCHARLIMIT) { last_width = fs->widths[c]; last_r2ladjust = fs->r2ladjusts[c]; } else { uschar utf[8]; tree_node *t; utf[misc_ord2utf8(c, utf)] = 0; t = Tree_Search(fs->high_tree, utf); if (t == NULL) { last_width = fs->widths[UNKNOWN_CHAR_S]; last_r2ladjust = fs->r2ladjusts[UNKNOWN_CHAR_S]; } else { last_width = t->val[1]; last_r2ladjust = t->val[2]; } } } /* Amass the total string width */ swidth += last_width; /* If there is another character, scan the kerning table */ if (main_kerning && fs->kerncount > 0 && *p != 0) { int bot = 0; int top = fs->kerncount; int cc; usint pair; GETCHAR(cc, p); pair = (c << 16) | cc; while (bot < top) { kerntablestr *k; int mid = (bot + top)/2; k = &(ktable[mid]); if (pair == k->pair) { swidth += k->kwidth; break; } if (pair < k->pair) top = mid; else bot = mid + 1; } } } /* For right-to-left, adjust the printing position for the string by the length of the string, adjusted for the actual bounding box of the final character, and scaled to the font size. For EPS output, adjust the bounding box. Both may, of course, happen. */ if (main_righttoleft) x += mac_muldiv(swidth - last_width + last_r2ladjust, pointsize, 1000); if (ps_EPS) { swidth = mac_muldiv(swidth, pointsize, 1000); if (x + swidth > out_bbox[2]) out_bbox[2] = x + swidth; } } /* Scan the string and generate the output. */ p = s; for (;;) { int c, pc; /* pc is the code value to print */ BOOL extended = FALSE; GETCHARINC(c, p); if (c == 0) break; /* For a non-standardly encoded font, see if there's a Unicode translation. If not, values greater than 255 are erroneous. Remember them, to be listed at the end. */ if (!stdencoding) { pc = font_utranslate(c, fs); if (pc >= 256) { pc = UNKNOWN_CHAR_N; /* Known to be < 256 */ if (uinvnext <= UUSIZE) { int i; for (i = 0; i < uinvnext; i++) if (invalid_unicode[i] == c) break; if (i >= uinvnext) { if (uinvnext < UUSIZE) invalid_unicode[uinvnext++] = c; else uinvoverflow = TRUE; } } } } /* Standardly encoded font */ else if (c < 256) pc = c; /* For standardly encoded fonts, code points >= 256 and < LOWCHARLIMIT are encoded in the second of the two PostScript fonts, using the Unicode encoding less 256. The remaining code points have to be converted to some of the remaining characters in the PostScript font, which are encoded arbitrarily, i.e. not using the Unicode encoding (some of their Unicode values are quite large). To find this encoding, we search for the character in the widths tree for the font, where the offset is also stored. */ else { if (c < LOWCHARLIMIT) { pc = c - 256; extended = TRUE; } else { uschar utf[8]; tree_node *t; utf[misc_ord2utf8(c, utf)] = 0; t = Tree_Search(fs->high_tree, utf); if (t == NULL) { pc = UNKNOWN_CHAR_S; if (uunext <= UUSIZE) { int i; for (i = 0; i < uunext; i++) if (unsupported_unicode[i] == c) break; if (i >= uunext) { if (uunext < UUSIZE) unsupported_unicode[uunext++] = c; else uuoverflow = TRUE; } } } else { pc = LOWCHARLIMIT + t->val[0] - 256; extended = TRUE; } } } /* Change between base and extended font if necessary */ if (ps_needchangefont(f, pointsize, extended)) { if (instring) { ps_endstring(absolute, w, x, y); x = y = 0; absolute = instring = FALSE; } ps_setfont(f, pointsize, extended); } /* Arrange to print the code */ if (!instring) { if (ps_chcount > 0 && ps_chcount + endp - p > 72) { fputc('\n', ps_file); ps_chcount = 0; } fputc('(', ps_file); ps_chcount++; instring = TRUE; } if (pc == '(' || pc == ')' || pc == '\\') ps_chcount += fprintf(ps_file, "\\%c", pc); else if (pc >= 32 && pc <= 126) { fputc(pc, ps_file); ps_chcount++; } else ps_chcount += fprintf(ps_file, "\\%03o", pc); /* If there is another character, scan the kerning table */ if (main_kerning && fs->kerncount > 0 && *p != 0) { int xadjust = 0, yadjust = 0; int bot = 0; int top = fs->kerncount; int cc; usint pair; GETCHAR(cc, p); pair = ((usint)c << 16) | cc; while (bot < top) { kerntablestr *k; int mid = (bot + top)/2; k = &(ktable[mid]); if (pair == k->pair) { xadjust = k->kwidth; break; } if (pair < k->pair) top = mid; else bot = mid + 1; } /* If a kern was found, scale the adjustment to the font size, and for the string rotation and transformation, if any. Then close the previous substring and arrange that the next be output relative if this is the first. */ if (xadjust != 0) { xadjust = mac_muldiv(xadjust, pointsize, 1000); yadjust = mac_muldiv(xadjust, font_transform[1], 65536); xadjust = mac_muldiv(xadjust, font_transform[0], 65536); ps_endstring(absolute, w, x, y); absolute = FALSE; instring = FALSE; x = main_righttoleft? -xadjust : xadjust; y = yadjust; } } } if (instring) ps_endstring(absolute, w, x, y); } /************************************************* * Output a string with space stretching * *************************************************/ /* Arguments: s the string font the font pointsize the pointsize for the font x the absolute x coordinate y the absolute y coordinate w the extra space for each space Returns: nothing */ void ps_wtext(uschar *s, int font, int pointsize, int x, int y, int w) { ps_basic_string(s, font, pointsize, TRUE, x, y, w); } /************************************************* * Output string in music font * *************************************************/ /* There are two versions, one with absolute coordinates, and one with relative coordinates, to save having to pass a flag each time (most of the calls are with absolute coordinates). Arguments: s the string pointsize the pointsize for the font x the absolute x coordinate y the absolute y coordinate Returns: nothing */ void ps_musstring(uschar *s, int pointsize, int x, int y) { ps_basic_string(s, font_mf, pointsize, TRUE, x, y, 0); } void ps_relmusstring(uschar *s, int pointsize, int x, int y) { ps_basic_string(s, font_mf, pointsize, FALSE, x, y, 0); } /************************************************* * Output a text string and change origin * *************************************************/ /* The x and y coordinates are updated if requested - note that y goes downwards. Arguments: s the string font the font pointsize the pointsize for the font x pointer to the absolute x coordinate y pointer to the absolute y coordinate update if TRUE, update the x,y positions Returns: nothing */ void ps_string(uschar *s, int f, int pointsize, int *x, int *y, BOOL update) { BOOL skip = FALSE; int truepointsize = (f == font_mu)? (pointsize*9)/10 : pointsize; /* Output the string, unless it is a music font string consisting only of printing point moving characters. */ if (f == font_mu || f == font_mf) { uschar *ss = s; skip = TRUE; while (*ss) { int ch; GETCHARINC(ch, ss); if (ch < 118 || (ch > 126 && ch < 185) || ch > 188) { skip = FALSE; /* break; */ } } } if (!skip) ps_basic_string(s, f, truepointsize, TRUE, *x, *y, font_xstretch); /* Now arrange to return the new current point if required */ if (update) { *x += font_stringwidth(s, f, pointsize); *y -= font_stringheight; } } /************************************************* * Output a bar line * *************************************************/ /* Arguments: x the x coordinate ytop the top of the barline ybot the bottom of the barline type the type of barline Returns: nothing */ void ps_barline(int x, int ytop, int ybot, int type) { int magn = (curmovt->barlinesize > 0)? curmovt->barlinesize : main_stavemagn; /* Normally, solid barlines and dashed ones of a single stave's depth are done with characters from the music font, except when the barline size is greater than the stave magnification and it's just one stave deep. However, the bar_use_draw option forces all bar lines to be drawn. */ if (!bar_use_draw && (type != bar_dotted || ytop == ybot) && (magn <= main_stavemagn || ytop != ybot)) { if (main_righttoleft) x += mac_muldiv(font_List[font_mu].r2ladjusts[type], 10*magn, 1000); if (ps_needchangefont(font_mf, 10*magn, FALSE)) ps_setfont(font_mf, 10*magn, FALSE); if (magn != main_stavemagn) ytop += 16*(magn - main_stavemagn); if (ytop != ybot) ps_printf(" %f %f(%c)%f %f b", 16*magn, psytran(ybot), type, psxtran(x), psytran(ytop)); else ps_printf("(%c)%f %f s", type, psxtran(x), psytran(ytop)); } /* Long dashed lines have to be drawn, as do other lines if they are shorter than the character - this happens if barlinesize is greater than the stave magnification - or if bar_use_draw is set. */ else { int half_thickness = (type == bar_thick)? magn : (type == bar_dotted)? magn/5 : (magn*3)/20; int yadjust = main_stavemagn/5; x += half_thickness; if (type == bar_dotted) ps_printf(" %f %f %f %f %f [%f %f] dl", psxtran(x), psytran(ytop - 16*main_stavemagn - yadjust), psxtran(x), psytran(ybot - yadjust), 2*half_thickness, 7*half_thickness, 7*half_thickness); else { ps_printf(" %f %f %f %f %f l", psxtran(x), psytran(ytop - 16*main_stavemagn - yadjust), psxtran(x), psytran(ybot - yadjust), 2*half_thickness); if (type == bar_double) { int xx = x + 2*magn; ps_printf(" %f %f %f %f %f l", psxtran(xx), psytran(ytop - 16*main_stavemagn - yadjust), psxtran(xx), psytran(ybot - yadjust), 2*half_thickness); } } } } /************************************************* * Output a brace * *************************************************/ /* Arguments: x the x coordinate ytop the y coordinate of the top of the brace ybot the y coordinate of the bottom of the brace magn the magnification Returns: nothing */ void ps_brace(int x, int ytop, int ybot, int magn) { ps_printf(" %f %f %f br%s", ((ybot-ytop+16*magn)*23)/12000, psxtran(x)+1500, psytran((ytop-16*magn+ybot)/2), (curmovt->bracestyle)? "2":""); } /************************************************* * Output a bracket * *************************************************/ /* Arguments: x the x coordinate ytop the y coordinate of the top of the bracket ybot the y coordinate of the bottom of the bracket magn the magnification Returns: nothing */ void ps_bracket(int x, int ytop, int ybot, int magn) { ps_printf(" %f %f %f k", psxtran(x), psytran(ytop)+16*magn, psytran(ybot)); } /************************************************* * Output a stave's lines * *************************************************/ /* The stavelines parameter will always be > 0. There is now an option to draw the stave lines rather than using characters for them (the default). This helps with screen displays that are using anti-aliasing. It has been reported that some PostScript interpreters can't handle the 100-point wide characters, so there is an option to use only the 10-point characters. Assume staves are always at least one character long. Arguments: leftx the x-coordinate of the stave start y the y-coordinate of the stave start rightx the x-coordinate of the stave end stavelines the number of stave lines Returns: nothing */ void ps_stave(int leftx, int y, int rightx, int stavelines) { uschar sbuff[16]; uschar buff[256]; int ch, i; int chwidth = 0; int x = leftx; /* Output the stave using PostScript drawing primitives. */ if (stave_use_draw > 0) { int gap; int thickness = (stave_use_draw*main_stavemagn)/10; if (ps_chcount > 0) ps_printf("\n"); switch(stavelines) { case 1: y -= 4*main_stavemagn; /* Fall through */ case 2: y -= 4*main_stavemagn; /* Fall through */ case 3: gap = 8*main_stavemagn; break; default: gap = 4*main_stavemagn; break; } ps_printf("%f %f %f %f %f %d ST\n", psxtran(x), psytran(y), rightx - leftx, thickness, gap, stavelines); ps_chcount = 0; return; } /* Output the stave using music font characters */ if (stave_use_widechars) { ch = out_stavechar10[stavelines]; i = 100; } else { ch = out_stavechar1[stavelines]; i = 10; } /* Select appropriate size of music font */ if (ps_needchangefont(font_mf, 10*main_stavemagn, FALSE)) ps_setfont(font_mf, 10*main_stavemagn, FALSE); /* Build character string of (optionally) 100-point & 10-point chars; some of them are non-printing and have to be octal-escaped. */ Ustrcpy(buff, "("); for (; i >= 10; i /= 10) { if (ch < 127) { sbuff[0] = ch; sbuff[1] = 0; } else sprintf(CS sbuff, "\\%03o", ch); chwidth = i*main_stavemagn; while (rightx - x >= chwidth) { Ustrcat(buff, sbuff); x += chwidth; } ch = out_stavechar1[stavelines]; } /* Now print it, forcing it onto a separate line (for human legibility). We use BIGNUMBER/2 because the routine adds the length to ps_chcount to check for overflow. */ Ustrcat(buff, ")"); if (ps_chcount) ps_chcount = BIGNUMBER/2; ps_printf("%s%f %f s", buff, psxtran(main_righttoleft? x:leftx), psytran(y)); /* If there's a fraction of 10 points left, deal with it */ if (x < rightx) ps_printf(" (%s)%f %f s", sbuff, psxtran(main_righttoleft? rightx : (rightx - chwidth)), psytran(y)); ps_printf("\n"); ps_chcount = 0; } /************************************************* * Output one musical character * *************************************************/ /* Certain musical characters are given identity numbers in a virtual music font that may or may not correspond directly to characters in the actual music font. The table called out_mftable_ps[] defines how they are to be printed. Arguments: x the x coordinate y the y coordinate ch the character's identity number pointsize the point size Returns: nothing */ void ps_muschar(int x, int y, int ch, int pointsize) { uschar s[10]; mfstr *p; int xfudge = 0; /* There may be a chain of strings/displacements */ for (p = out_mftable_ps[ch]; p != NULL; p = p->next) { usint c = p->ch; int i = 0; int nxfudge = 0; /* Nasty fudge for bracketed accidentals in right-to-left mode: when the brackets come as individual values, swap them round and fudge the spacing of the remaining chars. This is needed for flats, in practice. */ if (main_righttoleft) switch (c) { case 139: c = 140; nxfudge = -1600; break; case 140: c = 139; break; case 141: c = 142; nxfudge = -1600; break; case 142: c = 141; break; default: break; } /* Extract up to 4 music font characters from c */ while (c) { i += misc_ord2utf8(c & 255, s + i); c >>= 8; } s[i] = 0; ps_basic_string(s, font_mf, pointsize, TRUE, x + mac_muldiv(p->x, pointsize, 10000) + xfudge, y - mac_muldiv(p->y, pointsize, 10000), 0); xfudge += nxfudge; } } /************************************************* * Output a beam line * *************************************************/ /* This function is called several times for a multi-line beam, with the level number increasing each time. Information about the slope and other features is in beam_* variables. Arguments: x0 starting x coordinate, relative to start of bar x1 ending x coordinate, relative to start of bar level level number levelchange set nonzero for accellerando and ritardando beams Returns: nothing */ void ps_beam(int x0, int x1, int level, int levelchange) { int y0, y1; int sign = (beam_upflag)? (+1) : (-1); int depth = -main_stavemagn*((n_fontsize * sign * (int)(((double)curmovt->beamdepth) / cos(atan((double)beam_slope/1000.0))))/10000)/1000; y1 = y0 = out_ystave - beam_firstY + mac_muldiv(n_fontsize, (level - 1) * sign * 3 * main_stavemagn, 10000); y0 -= mac_muldiv(x0-beam_firstX, beam_slope, 1000); y1 -= mac_muldiv(x1-beam_firstX, beam_slope, 1000); /* For accellerando and ritardando beams, adjust the ends, and make a little bit thinner. */ if (levelchange != 0) { int adjust = mac_muldiv(n_fontsize, abs(levelchange) * sign * 4 * main_stavemagn, 10000); depth = (depth*17)/20; if (levelchange < 0) { y0 += adjust; y1 += adjust/8; } else { y0 += adjust/8; y1 += adjust; } } /* Get absolute x values and write the PostScript */ x0 += out_barx; x1 += out_barx; /* When printing right-to-left, adjust by one note's printing adjustment. The value can't just be read from the font, as it needs fiddling, so we just fudge a fixed value. */ if (main_righttoleft) { int adjust = sign * mac_muldiv(n_fontsize/2, main_stavemagn, 1000); x0 -= adjust; x1 -= adjust; } ps_printf(" %f %f %f %f %f m", depth, psxtran(x1), psytran(y1), psxtran(x0), psytran(y0)); } /************************************************* * Output a slur * *************************************************/ /* This was the original way of drawing slurs. Additional complication in slurs has resulted in a function called out_slur() that uses more primitive output functions, and which could in principle be used for all slurs. However, we retain ps_slur for complete, non-dashed, curved slurs for compatibility and to keep the size of the PostScript down. Arguments: x0 start x coordinate y0 start y coordinate x1 end x coordinate y1 end y coordinate flags slur flags co "centre out" adjustment Returns: nothing */ void ps_slur(int x0, int y0, int x1, int y1, int flags, int co) { int length = x1 - x0; y0 = out_ystave - y0; y1 = out_ystave - y1; x0 += 3*main_stavemagn; x1 += 3*main_stavemagn; co = ((co + ((length > 20000)? 6000 : (length*6)/20)) * main_stavemagn)/1000; if ((out_slurclx | out_slurcly | out_slurcrx | out_slurcry) != 0) { ps_printf(" %f %f %f %f cA", out_slurclx, out_slurcly, out_slurcrx, out_slurcry); ps_slurA = TRUE; } else if (ps_slurA) { ps_printf(" 0 0 0 0 cA"); /* default extra control movements */ ps_slurA = FALSE; } /* Keeping these as two separate calls enables the output to be split; changing would require test output to be reset. */ ps_printf(" %f %f %f %f", psxtran(x0), psytran(y0), psxtran(x1), psytran(y1)); ps_printf(" %f cv%s%s", ((flags & sflag_b) != 0)? (-co) : co, ((flags & sflag_w) == 0)? "" : "w", ((flags & sflag_e) == 0)? "" : "e"); } /************************************************* * Output a straight line * *************************************************/ /* Arguments: x0 start x coordinate y0 start y coordinate x1 end x coordinate y1 end y coordinate thickness line thickness flags for various kinds of line Returns: nothing */ void ps_line(int x0, int y0, int x1, int y1, int thickness, int flags) { uschar *reset = US""; double xx = (double)((int)(x1 - x0)); double yy = (double)((int)(y1 - y0)); double zz = sqrt(xx*xx + yy*yy); int len = (int)zz; /* Don't cast sqrt; it gives a compiler warning */ int dashlength = 0; int gaplength = 0; int dashcount, spacecount; /* Handle "editorial" lines: won't exist if dashed or dotted */ if ((flags & tief_editorial) != 0) { ps_printf(" GS %f %f T %f R 0 2.0 Mt 0 -2.0 Lt S GR", psxtran((x0+x1)/2), psytran(out_ystave - (y0+y1)/2), (int)(atan2(yy, xx)*180000.0/3.14159)); } /* Compute new dash parameters if required */ if ((flags & tief_dashed) != 0) { dashlength = 3*main_stavemagn; dashcount = (len/dashlength) | 1; spacecount = dashcount/2; if (dashcount != 1) { gaplength = (len - ((dashcount+1)*dashlength)/2)/spacecount; ps_printf("[%f %f] 0 Sd", dashlength, gaplength); reset = US"[] 0 Sd"; } } else if ((flags & tief_dotted) != 0) { dashlength = 100; dashcount = (len + 4*main_stavemagn)/(4*main_stavemagn + dashlength); if (dashcount > 1) { gaplength = (len - dashcount * dashlength)/(dashcount - 1); ps_printf(" 1 Slc[%f %f] 0 Sd", dashlength, gaplength); thickness = main_stavemagn; reset = US" 0 Slc[] 0 Sd"; } } /* If just set dash parameters, take note of the save flag. */ if (gaplength > 0) { if ((flags & tief_savedash) != 0) { reset = US""; out_dashlength = dashlength; out_dashgaplength = gaplength; } else out_dashlength = out_dashgaplength = 0; } /* Do the line */ ps_printf(" %f %f %f %f %f l%s", psxtran(x1), psytran(out_ystave - y1), psxtran(x0), psytran(out_ystave - y0), thickness, reset); } /************************************************* * Output a series of lines * *************************************************/ /* This is only used for sequences of plain lines (no dashes, etc.) Arguments: x vector of x coordinates y vector of y coordinates count number of vector elements thickness line thickness Returns: nothing */ void ps_lines(int *x, int *y, int count, int thickness) { int i; for (i = count - 1; i > 0; i--) ps_printf(" %f %f", psxtran(x[i]), psytran(out_ystave - y[i])); ps_printf(" %d %f %f %f ll", count - 1, psxtran(x[0]), psytran(out_ystave - y[0]), thickness); } /************************************************* * Output and stroke or fill a path * *************************************************/ /* The path can contain moves, lines, and curves. We generate in-line PostScript for this one, using the saved grey level. Arguments: x vector of x coordinates y vector of y coordinates c vector of move/line/curve operators thickness thickness of the lines for stroke; negative for fill Returns: nothing */ void ps_path(int *x, int *y, int *c, int thickness) { while (*c) switch(*c++) { case path_move: ps_printf(" %f %f Mt", psxtran(*x++), psytran(out_ystave - *y++)); break; case path_line: ps_printf(" %f %f Lt", psxtran(*x++), psytran(out_ystave - *y++)); break; case path_curve: ps_printf(" %f %f %f %f %f %f Ct", psxtran(x[0]), psytran(out_ystave - y[0]), psxtran(x[1]), psytran(out_ystave - y[1]), psxtran(x[2]), psytran(out_ystave - y[2])); x += 3; y += 3; break; } if (ps_gray != 0) ps_printf(" %f Sg", ps_gray); if (thickness >= 0) ps_printf(" %f Slw S", thickness); else ps_printf(" F"); if (ps_gray != 0) ps_printf(" 0 Sg"); } /************************************************* * Output and stroke or fill an absolute path * *************************************************/ /* This function (similar to the one above) is used for fancy slurs, when the coordinate system has been rotated and translated so that its origin is at the centre of the slur with the x axis joining the endpoints. The coordinates must therefore not use psxtran/psytran. Arguments: x vector of x coordinates y vector of y coordinates c vector of move/line/curve operators thickness thickness of the lines for stroke; negative for fill only Returns: nothing */ void ps_abspath(int *x, int *y, int *c, int thickness) { while (*c) switch(*c++) { case path_move: ps_printf(" %f %f Mt", *x++, *y++); break; case path_line: ps_printf(" %f %f Lt", *x++, *y++); break; case path_curve: ps_printf(" %f %f %f %f %f %f Ct", x[0], y[0], x[1], y[1], x[2], y[2]); x += 3; y += 3; break; } if (ps_gray != 0) ps_printf(" %f Sg", ps_gray); if (thickness >= 0) ps_printf(" %f Slw S", thickness); else ps_printf(" F"); if (ps_gray != 0) ps_printf(" 0 Sg"); } /************************************************* * Output a PostScript string * *************************************************/ /* This function is called to output a user-supplied PostScript string. Arguments: s the string x the x coordinate y the y coordinate Returns: nothing */ void ps_pstext(uschar *s, int x, int y) { ps_printf(" GS %f %f T\n%%User PostScript\n%s", psxtran(x), psytran(y), s); fprintf(ps_file, "\n%%End user PostScript\nGR\n"); ps_chcount = 0; } /************************************************* * Set gray level * *************************************************/ /* All that happens here is that the gray level is remembered for later use. Argument: the gray level Returns: nothing */ void ps_setgray(int gray) { ps_gray = gray; } /************************************************* * Set dash and capandjoin * *************************************************/ /* The set values are remembered so that repetition is avoided. Arguments: dashlength the dash length gaplength the gap length caj the cap-and-join value Returns: nothing */ void ps_setdash(int dashlength, int gaplength, int caj) { if (dashlength != out_dashlength || gaplength != out_dashgaplength) { if (dashlength == 0 && gaplength == 0) ps_printf("[] 0 Sd"); else ps_printf("[%f %f] 0 Sd", dashlength, gaplength); out_dashlength = dashlength; out_dashgaplength = gaplength; } if (caj != ps_caj) { if ((caj & caj_round) == caj_round) ps_printf(" 1 Slc"); else if ((caj & caj_square) == caj_square) ps_printf(" 2 Slc"); else ps_printf(" 0 Slc"); if ((caj & caj_round_join) == caj_round_join) ps_printf(" 1 Slj"); else if ((caj & caj_bevel_join) == caj_bevel_join) ps_printf(" 2 Slj"); else ps_printf(" 0 Slj"); ps_caj = caj; } } /************************************************* * Gsave and Grestore * *************************************************/ /* These functions are called from setslur.c when the coordinate system is translated and rotated for the drawing of a fancy slur. They translate directly into PostScript shorthand for gsave and grestore. Arguments: none Returns: nothing */ void ps_gsave(void) { ps_printf(" GS"); } void ps_grestore(void) { ps_printf(" GR"); } /************************************************* * Rotate * *************************************************/ /* This function rotates the coordinate system. Argument: the amount to rotate, in radians Returns: nothing */ void ps_rotate(double r) { if (r != 0.0) ps_printf(" %f R", (int)((r/(4.0 * atan(1.0)))*180000.0)); } /************************************************* * Translate * *************************************************/ /* This function translates the coordinate system. Arguments: x x coordinate of the new origin y y coordinate of the new origin Returns: nothing */ void ps_translate(int x, int y) { ps_printf(" %f %f T", psxtran(x), psytran(out_ystave - y)); } /************************************************* * Handle new movement * *************************************************/ /* The only thing this function does is to compute the value of the lefthand margin, which may change between movements. There is a fudge to the computation for the default value, in order to keep it the same as it was before the sheetwidth sizes were adjusted to be precisely the paper size, at least for A4 paper. Arguments: none Returns: nothing */ void ps_newmovt(void) { if (curmovt->leftmargin < 0) /* Default (not set by user) */ { ps_xmargin = (ps_sheetwidth - curmovt->linelength)/2 + 13000000/(2*main_magnification); if (ps_xmargin < 20000) ps_xmargin = 20000; } else ps_xmargin = curmovt->leftmargin; } /************************************************* * Start a given bar for a given stave * *************************************************/ /* Force a new line and output an identifying comment. Arguments: barnumber the bar number stave the stave */ void ps_startbar(int barnumber, int stave) { if (ps_chcount != 0) ps_chcount = BIGNUMBER/2; ps_printf("%%%b/%d\n", barnumber, stave); ps_chcount = 0; } /************************************************* * Include a file in the PostScript output * *************************************************/ /* This function is called for the main header file, and also for user-supplied head/foot or setup files. If the included file is an EPS file, the insert is wrapped in save/restore and the a4, showpage, and copypage commands are disabled. Certain lines in the header are included only when we are generating an EPS file. They are flagged in the header file with %EPS. Otherwise, if a line starts with %, it is copied only if it starts with %%. Blank lines are omitted. Arguments: s the file name relativize if TRUE, relativize non-absolute path to the current input file Returns: nothing */ static void ps_include(uschar *s, BOOL relativize) { FILE *f; uschar name[WORD_BUFFERSIZE]; Ustrcpy(name, s); if (relativize) sys_relativize(name, sizeof(name)); f = Ufopen(name, "r"); if (f != NULL) { uschar buff[256]; BOOL line1 = TRUE; BOOL insert_eps = FALSE; while (Ufgets(buff, 256, f) != NULL) { if (line1 && Ustrncmp(buff, "%!PS-Adobe", 10) == 0 && Ustrstr(buff, "EPSF-") != NULL) { insert_eps = TRUE; fputs("/epspicsave save def/a4{null pop}def\n", ps_file); fputs("/showpage{initgraphics}def/copypage{null pop}def\n", ps_file); } else { if (ps_EPS && Ustrncmp(buff, "%EPS ", 5) == 0) Ufputs(buff+5, ps_file); else if (buff[0] != '\n' && (buff[0] != '%' || buff[1] == '%')) Ufputs(buff, ps_file); } line1 = FALSE; } if (buff[Ustrlen(buff)-1] != '\n') fputc('\n', ps_file); if (insert_eps) fputs("epspicsave restore\n", ps_file); fclose(f); ps_chcount = 0; } else error_moan(ERR4, name, strerror(errno)); /* Hard error */ } /************************************************* * Output PostScript head/foot * *************************************************/ /* Output literal PostScript for a heading/footing. If the first character is '<', the remainder is the name of a file. Otherwise, it is literal PostScript. Argument: pointer to a headstr Returns: nothing */ void ps_headfoot(headstr *p) { fprintf(ps_file, "\n%%User PostScript\n"); if (p->text[0] == '<') ps_include(p->text + 1, TRUE); else fprintf(ps_file, "%s\n", p->text); fprintf(ps_file, "%%End user PostScript\n"); ps_chcount = 0; } /************************************************* * Include a font in the output * *************************************************/ /* Argument: name the name of the font ext a file extension or empty string Returns: nothing */ static void include_font(uschar *name, const char *ext) { FILE *f = NULL; char *s; uschar *fextra, *fdefault; uschar buff[256]; /* If this is one of the PMW fonts, seek it in the psfonts directories, otherwise look in the general fonts directories. */ if (Ustrncmp(name, "PMW-", 4) == 0) { fextra = font_music_extra; fdefault = font_music_default; } else { fextra = font_data_extra; fdefault = font_data_default; } /* font_finddata(..., TRUE) gives a hard error if the file cannot be found. */ f = font_finddata(name, ext, fextra, fdefault, buff, TRUE); /* Copy from "%%BeginResource:" or the start of the file to "%%EndResource" or the end of the file. */ while ((s = Ufgets(buff, sizeof(buff), f)) != NULL) if (Ustrncmp(buff, "%%BeginResource:", 16) == 0) break; if (s == NULL) { fprintf(ps_file, "\n%%%%BeginResource: font %s\n", name); rewind(f); } else fprintf(ps_file, "%s", CS buff); while ((s = Ufgets(buff, sizeof(buff), f)) != NULL) { fprintf(ps_file, "%s", CS buff); if (Ustrncmp(buff, "%%EndResource", 13) == 0) break; } if (s == NULL) fprintf(ps_file, "\n%%%%EndResource\n\n"); } /************************************************* * Produce PostScript output * *************************************************/ /* This is the controlling function for generating PostScript output. If the print_imposition has the special value pc_EPS, we are producing EPS PostScript, and a number of page-related parameters are then ignored. Arguments: none Returns: nothing */ void ps_go(void) { time_t timer; int fonts_to_include[MAX_FONTS]; int fonts_to_include_count = 0; int i, w = 0, d = 0; int count = 0; int fcount = 1; int scaled_main_sheetwidth = mac_muldiv(main_sheetwidth, print_magnification, 1000); /* Initialize the current page number and page list data */ ps_EPS = (print_imposition == pc_EPS); print_setup_pagelist(ps_EPS? FALSE : print_reverse); /* Set the top of page y coordinate; the PostScript is relative to the usual bottom of page origin. Before the invention of the imposition parameter, we computed this from the pagelength, but with some minima imposed. For compatibility, keep this unchanged for cases when imposition is defaulted. For EPS, we use the sheetsize, whatever it may be. */ if (ps_EPS) ps_ymax = main_truepagelength + 50000; else { if (opt_landscape) { if (main_truepagelength < 492000) ps_ymax = mac_muldiv(526000, 1000, print_magnification); else ps_ymax = main_truepagelength + 34000; } else { if (main_truepagelength < 720000) ps_ymax = mac_muldiv(770000, 1000, print_magnification); else ps_ymax = main_truepagelength + 50000; } /* Take the opportunity of setting true paper sizes for imposing */ switch(print_imposition) { case pc_a5ona4: w = 595000; d = 842000; ps_ymax = main_truepagelength + 50000; break; case pc_a4ona3: w = 842000; d = 1190000; ps_ymax = main_truepagelength + 50000; break; } } /* Adjust paper size to the magnification */ ps_sheetwidth = mac_muldiv(main_sheetwidth, 1000, main_magnification); ps_ymax = mac_muldiv(ps_ymax, 1000, main_magnification); /* Initializing stuff at the start of the PostScript file. We are attempting to keep to the 3.0 structuring conventions. Initial comments ("header") come first. */ time (&timer); fprintf(ps_file, "%%!PS-Adobe-3.0%s\n", ps_EPS? " EPSF-3.0" : ""); fprintf(ps_file, "%%%%Creator: Philip's Music Writer %s\n", version_string); fprintf(ps_file, "%%%%CreationDate: %s", ctime(&timer)); if (ps_EPS) fprintf(ps_file, "%%%%BoundingBox: (atend)\n"); else fprintf(ps_file, "%%%%Pages: (atend)\n"); fprintf(ps_file, "%%%%DocumentNeededResources: font "); /* Scan the fonts, set the ps id and process each unique one, remembering those that are to be included in the output. */ for (i = 0; i < font_tablen; i++) { fontstr *fs; int fontid, j; for (j = 0; j < i; j++) if (font_table[i] == font_table[j]) break; ps_IdStrings[i] = font_IdStrings[j]; if (j != i) continue; /* Seen this one already */ if (++fcount > 3) { fprintf(ps_file, "\n%%%%+ font "); fcount = 1; } fontid = font_table[i]; fs = font_List + fontid; fprintf(ps_file, "%s ", fs->psname); /* Remember which fonts are to be included. If -incPMWfont was set, do this automatically for music fonts. */ if (fs->include || (output_incPMWfont && (Ustrcmp(fs->psname, "PMW-Music") == 0 || Ustrcmp(fs->psname, "PMW-Alpha") == 0))) fonts_to_include[fonts_to_include_count++] = fontid; } fprintf(ps_file, "\n"); /* List the included fonts */ if (fonts_to_include_count > 0) { fcount = 1; fprintf(ps_file, "%%%%DocumentSuppliedResources: font"); for (i = 0; i < fonts_to_include_count; i++) { if (++fcount > 3) { fprintf(ps_file, "\n%%%%+ font"); fcount = 1; } fprintf(ps_file, " %s", (font_List[fonts_to_include[i]]).psname); } fprintf(ps_file, "\n"); } if (!ps_EPS) fprintf(ps_file, "%%%%Requirements: numcopies(%d)\n", output_copies); fprintf(ps_file, "%%%%EndComments\n\n"); /* Deal with a known paper size */ switch (opt_sheetsize) { case sheet_A3: fprintf(ps_file, "%%%%BeginPaperSize: a3\na3\n%%%%EndPaperSize\n\n"); break; case sheet_A4: fprintf(ps_file, "%%%%BeginPaperSize: a4\na4\n%%%%EndPaperSize\n\n"); break; case sheet_A5: fprintf(ps_file, "%%%%BeginPaperSize: a5\na5\n%%%%EndPaperSize\n\n"); break; case sheet_B5: fprintf(ps_file, "%%%%BeginPaperSize: b5\nb5\n%%%%EndPaperSize\n\n"); break; case sheet_letter: fprintf(ps_file, "%%%%BeginPaperSize: letter\nletter\n%%%%EndPaperSize\n\n"); break; default: break; } /* Next, the file's prologue */ fprintf(ps_file, "%%%%BeginProlog\n"); /* If there is a header file, copy it now. Its name is NOT relative to the main input file. (If it is not absolute, it is taken relative to the current directory.) */ if (ps_header != NULL) ps_include(ps_header, FALSE); /* Deal with any requested PostScript setup */ if (main_pssetup != NULL) { headstr *h = main_pssetup; fprintf(ps_file, "\n%% Included pssetup strings and/or files\n"); while (h != NULL) { if (h->text[0] == '<') ps_include(h->text + 1, TRUE); else fprintf(ps_file, "%s\n", h->text); h = h->next; } fprintf(ps_file, "\n"); } fprintf(ps_file, "%%%%EndProlog\n\n"); /* The setup section sets up the printing device. We include the font finding in here, as it seems the right place. Include any relevant fonts in the output file. */ fprintf(ps_file, "%%%%BeginSetup\n"); for (i = 0; i < fonts_to_include_count; i++) { uschar *psname = font_List[fonts_to_include[i]].psname; include_font(psname, (Ustrcmp(psname, "PMW-Alpha") == 0)? "" : ".pfa"); } /* Now set up the fonts */ for (i = 0; i < font_tablen; i++) { int j; for (j = 0; j < i; j++) if (font_table[i] == font_table[j]) break; if (j == i) { fontstr *f = font_List + font_table[i]; uschar *s = f->psname; fprintf(ps_file, "%%%%IncludeResource: font %s\n", s); fprintf(ps_file, "/%s /%sX /%s inf\n", font_IdStrings[i], font_IdStrings[i], s); } } /* Unless EPS, we used to select A4 paper, but only once (to allow concatenated files). However, this seems to give trouble with Ghostview for doing magnify windows, and it doesn't seem to affect modern PostScript printers anyway. So it is no longer done. Select the number of copies if not 1, set manual feed if the flag is set, deal with duplex and tumble options, and end the setup section. */ if (!ps_EPS) { /********* fprintf(ps_file, "currentdict /a4_done known not {a4 /a4_done true def} if\n"); **********/ if (output_copies != 1) fprintf(ps_file, "/#copies %d def\n", output_copies); if (output_manualfeed || output_duplex) { fprintf(ps_file, "statusdict begin"); if (output_manualfeed) fprintf(ps_file, " /manualfeed true def"); if (output_duplex) { fprintf(ps_file, " true setduplexmode"); if (output_tumble) fprintf(ps_file, " true settumble"); } fprintf(ps_file, " end\n"); } } fprintf(ps_file, "%%%%EndSetup\n\n"); /* Now the requested pages. The print_nextpage function returns one or two pages. When printing 2-up either one of them may be null. */ for (;;) { int scaled = 1000; BOOL recto = FALSE; sysblock *s; pagestr *ps_1stpage, *ps_2ndpage; if (!print_nextpage(&ps_1stpage, &ps_2ndpage)) break; if (ps_1stpage != NULL && ps_2ndpage != NULL) fprintf(ps_file, "%%%%Page: %d&%d %d\n", ps_1stpage->number, ps_2ndpage->number, ++count); else if (ps_1stpage != NULL) { fprintf(ps_file, "%%%%Page: %d %d\n", ps_1stpage->number, ++count); recto = (ps_1stpage->number & 1) != 0; } else { fprintf(ps_file, "%%%%Page: %d %d\n", ps_2ndpage->number, ++count); recto = (ps_2ndpage->number & 1) != 0; } fprintf(ps_file, "%%%%BeginPageSetup\n/pagesave save def\n"); ps_curfont = -1; ps_curfontX = FALSE; ps_chcount = 0; ps_caj = 0; if (ps_EPS) { if (main_righttoleft) ps_printf("%f 0 T -1 1 scale\n", main_sheetwidth); if (main_magnification != 1000) ps_printf("%f dup scale\n", main_magnification); } else { if (main_righttoleft) { pagestr *temp = ps_1stpage; ps_1stpage = ps_2ndpage; ps_2ndpage = temp; } /* Move the origin to the desired position. The values 1 (upright, 1-up, portrait), 2 (sideways, 2-up, portrait), and 4 (sideways, 1-up, landscape) use bottom left, i.e. no translation, but we have to generate an adjustment for type 2 if sheetwidth isn't half the paper size. The gutter facility is available only when printing 1-up. */ switch (print_pageorigin) { case 0: /* A4 Sideways, 1-up, portrait */ ps_printf("0 %f T -90 R\n", 595000); if (print_gutter != 0) ps_printf("%f 0 T\n", recto? print_gutter : -print_gutter); break; case 1: /* Upright, 1-up, portrait */ if (print_gutter != 0) ps_printf("%f 0 T\n", recto? print_gutter : -print_gutter); break; case 2: /* Sideways, 2-up, portrait */ if (d/2 != scaled_main_sheetwidth) ps_printf("%f 0 T\n", (d/2 - scaled_main_sheetwidth)/(print_pamphlet? 1:2)); break; case 3: /* Upright, 2-up, portrait */ ps_printf("0 %f T -90 R\n", d - (d/2 - scaled_main_sheetwidth)/(print_pamphlet? 1:2)); break; case 4: /* A4 Sideways, 1-up, landscape */ if (print_gutter != 0) ps_printf("%f 0 T\n", recto? print_gutter : -print_gutter); break; case 5: /* Upright, 1-up, landscape; page size defined by sheetsize */ /* Sheetwidth is original sheet height */ ps_printf("0 %f T -90 R\n", scaled_main_sheetwidth); break; case 6: /* A4 Sideways, 2-up, landscape */ ps_printf("%f %f T -90 R\n", d/2, w); break; case 7: /* Upright, 2-up, landscape */ ps_printf("0 %f T\n", d/2); break; } if (print_image_xadjust != 0 || print_image_yadjust != 0) ps_printf("%f %f T\n", print_image_xadjust, print_image_yadjust); if (main_righttoleft) ps_printf("%f 0 T -1 1 scale\n", scaled_main_sheetwidth); if (main_magnification != 1000 || print_magnification != 1000) { scaled = mac_muldiv(main_magnification, print_magnification, 1000); ps_printf("%f dup scale\n", scaled); } } /* End of setup */ fprintf(ps_file, "%%%%EndPageSetup\n"); s = curpage->sysblocks; if (s != NULL) curmovt = s->movt; ps_newmovt(); /* When printing 2-up, we may get one or both pages; when not printing 2-up, we may get either page given, but not both. */ if (ps_1stpage != NULL) { curpage = ps_1stpage; out_page(); } if (ps_2ndpage != NULL) { if (ps_chcount > 0) fprintf(ps_file, "\n"); if (print_imposition == pc_a5ona4 || print_imposition == pc_a4ona3) { int sign = main_righttoleft? -1 : +1; int dd = mac_muldiv(d, 500, scaled); if (opt_landscape) ps_printf("0 %f T\n", -dd); else ps_printf("%f 0 T\n", sign * (print_pamphlet? mac_muldiv(main_sheetwidth, 1000, main_magnification) : dd)); } curpage = ps_2ndpage; out_page(); } /* EPS files are permitted to contain showpage, and this is actually useful because it means an EPS file can be printed or displayed. So we don't cut out showpage. */ fprintf(ps_file, "\npagesave restore showpage\n\n"); } /* Do PostScript trailer */ fprintf(ps_file, "%%%%Trailer\n"); if (ps_EPS) { if (main_righttoleft) ps_printf("%%%%BoundingBox: %f %f %f %f\n", main_sheetwidth - mac_muldiv(psxtran(out_bbox[2]), main_magnification, 1000), mac_muldiv(psytran(out_bbox[1]), main_magnification, 1000), main_sheetwidth - mac_muldiv(psxtran(out_bbox[0]), main_magnification, 1000), mac_muldiv(psytran(out_bbox[3]), main_magnification, 1000)); else ps_printf("%%%%BoundingBox: %f %f %f %f\n", mac_muldiv(psxtran(out_bbox[0]), main_magnification, 1000), mac_muldiv(psytran(out_bbox[1]), main_magnification, 1000), mac_muldiv(psxtran(out_bbox[2]), main_magnification, 1000), mac_muldiv(psytran(out_bbox[3]), main_magnification, 1000)); } else fprintf(ps_file, "%%%%Pages: %d\n", count); /* Warn for unsupported Unicode code points */ if (uunext > 0) { error_moan(ERR79, (uunext == 1)? " has":"s have", UNKNOWN_CHAR_S); fprintf(stderr, " "); while (uunext > 0) fprintf(stderr, " U+%04X", unsupported_unicode[--uunext]); if (uuoverflow) fprintf(stderr, " ..."); fprintf(stderr, "\n"); if (main_rc < rc_warning) main_rc = rc_warning; } /* Warn for invalid Unicode code points in non-standardly encode fonts*/ if (uinvnext > 0) { error_moan(ERR78, (uinvnext == 1)? " has":"s have", UNKNOWN_CHAR_N); fprintf(stderr, " "); while (uinvnext > 0) fprintf(stderr, " U+%04X", invalid_unicode[--uinvnext]); if (uinvoverflow) fprintf(stderr, " ..."); fprintf(stderr, "\n"); if (main_rc < rc_warning) main_rc = rc_warning; } } /* End of ps.c */