/*************************************************
*    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 */
