/* psgen.c - routines for the postscript generation
   Copyright 1988-2017 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, see
   <https://www.gnu.org/licenses/gpl-3.0.html>.  */

#include <config.h>

#include "a2ps.h"		/* most global variables 		*/
#include "prolog.h"
#include "psgen.h"
#include "routines.h"		/* general interest routines		*/
#include "output.h"		/* Diverted outputs			*/
#include "media.h"
#include "faces.h"
#include "metaseq.h"
#include "message.h"
#include "fonts.h"
#include "dsc.h"
#include "jobs.h"
#include "fjobs.h"
#include "prange.h"

#define PRINT_HEADER					\
	(!IS_EMPTY(job->header))

#define PRINT_FOOTER					\
	(!IS_EMPTY(job->footer)				\
	 || !IS_EMPTY(job->left_footer)			\
	 || !IS_EMPTY(job->right_footer))

#define PRINT_TITLE					\
	(!IS_EMPTY(job->center_title)			\
	 || !IS_EMPTY(job->left_title)			\
	 || !IS_EMPTY(job->right_title))

#define jdiv	job->divertion

/* return the max of two >-comparable stuff */
#define A2_MAX(X,Y)        (((X) > (Y)) ? (X) : (Y))


/****************************************************************/
/*		Formating help routines				*/
/****************************************************************/
/*
 * Print a char in a form accepted by postscript printers.
 * Returns number of columns used (on the output) to print the char.
 */
static unsigned
ps_escape_char (a2ps_job * job, unsigned char c, char * res)
{
  unsigned len = 0;

  /* The number of columns used must be calculated here because of the
   * \ before non-ascii chars, and (, ), and \ itself */

  /* Printable and 7bit clean characters */
  if (' ' <= c && c < 0177) {
    if (c == '(' || c == ')' || c == '\\')
      STRCCAT(res, '\\');
    STRCCAT(res, c);
    return 1;
  }

  /* Printable, but not 7-bit clean characters */
  if (encoding_char_exists (job->encoding, job->status->face, (char) c)
      && ((0177 < c) || (c < 040))) {
    char oct[5];
    int chars = sprintf (oct, "\\%o", c);
    for (int i = 0; i < chars; i++)
      STRCCAT(res, oct[i]);
    return 1;
  }

  /* We are dealing with a non-printing character */
  job->status->nonprinting_chars++;

  /* We do this in here for speed.  We could have first escaped, then
    consider the new string as the string to ps-escape. */
  switch (job->unprintable_format) {
  case octal:
    sprintf (res, "\\\\%03o", c);
    return 4;

  case hexa:
    sprintf (res, "\\\\x%02x", c);
    return 4;

  case question_mark:
    STRCCAT (res, '?');
    return 1;

  case space:
    STRCCAT (res, ' ');
    return 1;

  case caret:
    if (0177 < c) {
      strcat(res, "M-");
      len += 2;
      c &= 0177;
    }

    if (c < ' ') {
      STRCCAT(res, '^');
      len += 2;
      c = (unsigned char) ((char)c + '@');
      if (c == '(' || c == ')' || c == '\\')
	STRCCAT(res, '\\');
      STRCCAT(res, c);
    } else if (c == 0177) {
      strcat(res, "^?");
      len += 2;
    } else {
      if (c == '(' || c == ')' || c == '\\')
	STRCCAT(res, '\\');
      STRCCAT(res, c);
      len++;
    }
    return len;

  case Emacs:
    if (0177 < c) {
      strcat(res, "M-");
      len += 2;
      c &= 0177;
    }

    if (c < ' ') {
      strcat (res, "C-");
      len += 3;
      c = (unsigned char) ((char)c + '@');
      if (c == '(' || c == ')' || c == '\\')
	STRCCAT(res, '\\');
      STRCCAT(res, c);
    } else if (c == 0177) {
      strcat(res, "C-?");
      len += 3;
    } else {
      if (c == '(' || c == ')' || c == '\\')
	STRCCAT(res, '\\');
      STRCCAT(res, c);
      len++;
    }
    return len;

  }
  return 0;
}

/*
 * Output the formated marker.
 */
static void
output_marker (a2ps_job * job, const char * kind, char * marker)
{
  char *cp, buf[256];
  int i;

  cp = expand_user_string (job, CURRENT_FILE (job), kind, marker);

  for (i = 0 ; cp[i] ; i++) {
    switch (cp[i]) {
    case FILE_LAST_PAGE:
      output_delayed_size_t (jdiv, &(CURRENT_FILE (job)->last_page));
      break;
    case FILE_LAST_SHEET:
      output_delayed_size_t (jdiv, &(CURRENT_FILE (job)->last_sheet));
      break;
    case FILE_NB_PAGES:
      output_delayed_size_t (jdiv, &(CURRENT_FILE (job)->pages));
      break;
    case FILE_NB_SHEETS:
      output_delayed_size_t (jdiv, &(CURRENT_FILE (job)->sheets));
      break;
    case FILE_NB_LINES:
      output_delayed_size_t (jdiv, &(CURRENT_FILE (job)->lines));
      break;
    case JOB_NB_PAGES:
      output_delayed_size_t (jdiv, &job->pages);
      break;
    case JOB_NB_SHEETS:
      output_delayed_size_t (jdiv, &job->sheets);
      break;
    case JOB_NB_FILES:
      output_delayed_size_t (jdiv, &job->total_files);
      break;
    default:
      *buf = '\0';
      ps_escape_char (job, (unsigned char) cp[i], buf);
      output (jdiv, "%s", buf);
      break;
    }
  }
}

/****************************************************************/
/*		Dealing with the encodings			*/
/****************************************************************/
/*
 * Change the current encoding
 */
void
ps_set_encoding (a2ps_job * job, struct encoding * enc)
{
  set_encoding (job, enc);
  job->status->opened_encoding = enc;
  output (jdiv, "%sdict begin\n", encoding_get_key (enc));
}

void
ps_end_encoding (a2ps_job * job)
{
  if (job->status->opened_encoding)
    output (jdiv, "end %% of %sdict\n",
	    encoding_get_key (job->status->opened_encoding));
  set_encoding (job, NULL);
  job->status->opened_encoding = NULL;
}

/*
 * Switch of encoding while we are _not_ in the body.  E.g., we
 * are dealing with the headers.
 *
 * We want to save the previous encoding, because, say we
 * were printing -X latin1, we have switched to latin2 dynamically.
 * Right now, we need to change page,
 * so we are using back latin1, but we must remember of latin2
 * for the following page.
 */
static void
ps_internal_switch_encoding (a2ps_job * job, struct encoding * enc)
{
  if (job->encoding != enc) {
    /* If there are no dictionary opened yet, it is because we are
     * changing of dict outside the text body (e.g. even before
     * the first output char).  In this case, do not open a
     * ps dict: it is not time yet.
     */
    if (job->status->opened_encoding) {
      ps_end_encoding (job);
      ps_set_encoding (job, enc);
    } else {
      ps_end_encoding (job);
      set_encoding (job, enc);
    }
    /* Make sure to re-declare the font */
    job->status->face_declared = false;
  }
}

/*
 * Switch the encoding during the print process (ie. from
 * within the body of the text)
 * We have to take care of closing the line currently being printed
 * and restore the state, i.e. opening a string to print.
 */
void
ps_switch_encoding (a2ps_job * job, struct encoding * enc)
{
  if (job->encoding != enc)
    {
      if (!job->status->start_line) {
	if (!job->status->face_declared)
	  output (jdiv, ") %s\n", face_eo_ps (job->status->face));
	else
	  output (jdiv, ") S\n");
      }
      ps_internal_switch_encoding (job, enc);
      job->saved_encoding = enc;
      if (!job->status->start_line)
	output_char (jdiv, '(');
    }
}

static void
ps_push_encoding (a2ps_job * job, struct encoding * enc)
{
  job->saved_encoding = job->encoding;
  ps_internal_switch_encoding (job, enc);
}

static void
ps_pop_encoding (a2ps_job * job)
{
  ps_internal_switch_encoding (job, job->saved_encoding);
}

/****************************************************************/
/*		Printing a physical page			*/
/****************************************************************/
/*
 * Print the prolog necessary for printing each physical page.
 * Adobe convention for page independence is enforced through this routine.
 * Note that is may called for a second time on a single sheet if two
 * different files are printed on the same sheet.
 */
static void
page_begin (a2ps_job * job)
{
  job->virtual = 1;

  if (print_page (job, job->pages))
    job->sheets++;

  /* The number of pages has not been updated yet: add 1 */
  CURRENT_FILE (job)->top_page = CURRENT_FILE (job)->pages + 1;
  /*
   * Getting ready to delay the page label
   * This means to build a new string handler, store it, and fill it later
   * We do this because one would like to have all the information on
   * what is on the current page before making the page label.
   */
  if (!output_is_to_void (jdiv))
    {
      output (jdiv, "%%%%Page: (");
      job->status->page_label = XMALLOC (char *);
      output_delayed_string (jdiv, job->status->page_label);
      output (jdiv, ") %zu\n", job->sheets);
    }

  /* Reinitialize state variables for each new sheet */
  output (jdiv, "%%%%BeginPageSetup\n");
  output (jdiv, "/pagesave save def\n");

  /* Shift front side sheets */
  if (job->margin
      && (job->duplex == simplex || (job->sheets % 2)))
    output (jdiv, "%u 0 translate\n", job->margin);

  if (job->orientation == landscape)
    output (jdiv, "sh 0 translate 90 rotate\n");

  output (jdiv, "%%%%EndPageSetup\n");

  if (job->debug)
    output (jdiv, "\
%% Display the bounding box\n\
  gsave\n\
    llx lly moveto\n\
    2 setlinewidth\n\
    0.9 setgray\n\
    urx lly lineto\n\
    urx ury lineto\n\
    llx ury lineto\n\
    closepath stroke\n\
  grestore\n\n");

  /* Set the encoding */
  ps_set_encoding (job, job->requested_encoding);

  /* water marks (only once per sheet) */
  if (!IS_EMPTY(job->water))
    {
      output_char (jdiv, '(');
      output_marker (job, "water mark", job->water);
      output (jdiv,
	      ") %4.2f water\n",
	      (atan2 ((double) job->medium->w - job->margin,
                      (double) job->medium->h)
	       / 3.14159265 * 180));
    }

  /* Move to the lower left point of the drawable area */
  output (jdiv, "gsave\n");
  output (jdiv, "llx lly %u add translate\n",
	  PRINT_FOOTER * HEADERS_H);
  /* Set the encoding */
  ps_internal_switch_encoding (job, job->saved_encoding);
}

/* The job on the page is over: puts the headers and footers,
 * then print the physical page.
 */
static void
page_end (a2ps_job * job)
{
  /* The page label has been divertered (through a handler).
   * Fill that handler with the correct page label value
   */
  *(job->status->page_label) =
    xstrdup (expand_user_string (job, CURRENT_FILE (job),
				  "Page label",
				  job->status->page_label_format));

  output (jdiv, "grestore\n");

  /* All the headers should be written using the requested encoding */
  ps_push_encoding (job, job->requested_encoding);

  /* Print the right header */
  if (PRINT_HEADER) {
    output_char (jdiv, '(');
    output_marker (job, "right header", job->header);
    output (jdiv, ") rhead\n");
  }

  /* Print the center footer.
   * Use dynamic markers in priority
   */
  if (PRINT_FOOTER) {
    if (!IS_EMPTY(job->footer)) {
      output_char (jdiv, '(');
      output_marker (job, "center footer", job->footer);
      output (jdiv, ") ");
    } else
      output (jdiv, "() ");

    /* Print the right footer */
    if (!IS_EMPTY(job->right_footer)) {
      output_char (jdiv, '(');
      output_marker (job, "right footer", job->right_footer);
      output (jdiv, ") ");
    } else
      output (jdiv, "() ");

    /* Print the left footer */
    if (!IS_EMPTY(job->left_footer)) {
      output_char (jdiv, '(');
      output_marker (job, "left footer", job->left_footer);
      output (jdiv, ") ");
    }
    else
      output (jdiv, "() ");
    output (jdiv, "footer\n");
  }

  /* Close the current encoding */
  ps_end_encoding (job);

  output (jdiv, "pagesave restore\n");
  output (jdiv, "showpage\n");

  job->virtual = 0;
}

/****************************************************************/
/*		Printing a virtual page				*/
/****************************************************************/
/*
 * Prints page header and page border and
 * initializes printing of the file lines.
 */
static void
virtual_begin (a2ps_job * job)
{
  job->pages++;

  if (print_page (job, job->pages)) {
    output_to_void (jdiv, false);
    job->virtual++;
  } else {
    output_to_void (jdiv, true);
  }

  /* We test to one, since it has just been incremented above */
  if (job->virtual == 1)
    page_begin (job);

  if (!job->encoding)
    ps_set_encoding (job, job->saved_encoding);

  file_job_synchronize_pages (job);
  file_job_synchronize_sheets (job);

  output (jdiv, "/v %u store\n", job->virtual - 1);
  output (jdiv, "/x0 x v get %f add sx cw mul add store\n",
	  SIDE_MARGIN_RATIO * (double) job->fontsize * 0.6);
  output (jdiv, "/y0 y v get bfs %s sub store\n",
	  PRINT_TITLE ? "th add" : "");
  output (jdiv, "x0 y0 moveto\n");
}

/*
 * Adds a sheet number to the page (footnote) and prints the formatted
 * page (physical impression). Activated at the end of each source page.
 */
static void
virtual_end (a2ps_job * job)
{
  /*
   *	Print the titles with the option-given encoding
   */
  /* Draw the header and its content */
  if (PRINT_TITLE) {
    ps_push_encoding (job, job->requested_encoding);
    if (!IS_EMPTY(job->center_title)) {
      output_char (jdiv, '(');
      output_marker (job, "center title", job->center_title);
      output (jdiv, ") ");
    } else
      output (jdiv, "() ");

    if (!IS_EMPTY(job->right_title)) {
      output_char (jdiv, '(');
      output_marker (job, "right title", job->right_title);
      output (jdiv, ") ");
    } else
      output (jdiv, "() ");

    if (!IS_EMPTY(job->left_title)) {
      output_char (jdiv, '(');
      output_marker (job, "left title", job->left_title);
      output (jdiv, ") ");
    } else
      output (jdiv, "() ");

    output (jdiv, "title\n");
    ps_pop_encoding (job);
  }

  if (job->border)
    output (jdiv, "border\n");

  if (job->virtual == (job->columns * job->rows))
    page_end (job);

  job->status->line = 0;
}

/* Issue an empty virtual (used for file alignment). */

static void
virtual_empty_output (a2ps_job *job)
{
  /* Let's use a hidden option which lets choose between an empty
     virtual (with headers etc.), or nothing printed at all. */
  if (macro_meta_sequence_get (job, VAR_OPT_VIRTUAL_FORCE))
    {
      virtual_begin (job);
      virtual_end (job);
    }
  else
    {
      /* Just increase the virtual number, that's enough. */
      job->pages++;
      job->virtual++;
      if (job->virtual == (job->columns * job->rows))
	page_end (job);
    }
}

/*
 * Flush the (physical) page, ready for new page
 * Used
 * -- at the end of the whole job
 * -- at the end of a page
 * -- when a fresh page is required (eg, when after an a2ps
 *    generated ps page arrives a delegated ps file)
 */
void
page_flush (a2ps_job * job)
{
  /* Everything that follows _must_ be dumped */
  output_to_void (job->divertion, false);

  /* If the sheet has not been printed, flush it */
  if (job->virtual != 0)
    page_end (job);
}

/* Output an empty page (used for instance to align files.
   No page should be waiting (issue flushes before). */

static void
page_empty_output (a2ps_job *job)
{
  job->sheets++;
  file_job_synchronize_sheets (job);
  output (jdiv, "%%%%Page: (*) %zu\n", job->sheets);
  output (jdiv, "%% Empty Page\n");
  output (jdiv, "showpage\n");
}

static void
sheet_flush (a2ps_job *job)
{
  page_flush (job);

  /* Need an empty back side? */
  if ((job->duplex == duplex || job->duplex == tumble)
      && (job->sheets % 2) != 0)
    page_empty_output (job);
}

/*
 * Make sure this a new sheet.
 * No DSC is done on the new page.  This is used when delegated
 * ps file is inlined.
 */
void
require_fresh_page (a2ps_job * job)
{
  /* If this is not a blank sheet, end it */
  if (job->virtual != 0)
    /* The clean up _must_ be done */
    page_flush (job);
}

/****************************************************************/
/*		Service routines				*/
/****************************************************************/
/*
 * Test if we have a binary file.
 *
 * Printing binary files is not very useful. We stop printing
 * if we detect one of these files. Our heuristic to detect them:
 * if 40% characters are non-printing characters,
 * the file is a binary file. (40% is taken from the case of a2ps istself).
 */
static void
check_binary_file (a2ps_job * job)
{
  if (job->status->chars > 120)
    {
      if (!job->print_binaries
	  && (job->status->nonprinting_chars * 100 / job->status->chars) >= 40)
	error (1, 0, _("`%s' is a binary file, printing aborted"),
	       CURRENT_FILE (job)->name);
    }
}

static inline void
end_of_line (struct a2ps_job * job)
{
  if (!job->status->face_declared) {
    output (jdiv, ") %s n\n", face_eo_ps (job->status->face));
    job->status->face_declared = true;
  } else
    output (jdiv, ") N\n");
  job->status->line++;
  job->status->column = 0;
  job->status->wx = 0;
}

#define page_full	\
	(job->status->line >= job->status->linesperpage)

#define line_full (job->status->wx > job->status->wxperline)

/*
 * Fold a line too long.
 */
static inline void
fold_line (struct a2ps_job * job, enum face_e new_face)
{
  job->lines_folded++;
  end_of_line (job);
  if (page_full)
    {
      virtual_end (job);
      virtual_begin (job);
      job->status->face_declared = false ;
    }
  if (job->numbering)
    {
      output (jdiv, "0 T (");
    }
  else
    {
      /* This is a new line, hence we can consider that there is no
	 need to close the current font: just consider it is the new
	 font, but not declared. */
      output_char (jdiv, '(');
      job->status->face_declared &= (job->status->face
				     == new_face);
      job->status->face = new_face;
  }
}

/*
 * Print a char
 */
void
ps_print_char (a2ps_job * job, char c, enum face_e new_face)
{
  /*
   * Preprocessing (before printing):
   * - TABs expansion (see interpret option)
   * - FF and BS interpretation
   * - replace non printable characters by a space or a char sequence
   *   like:
   *     ^X for ascii codes < 0x20 (X = [@, A, B, ...])
   *     ^? for del char
   *     M-c for ascii codes > 0x3f
   * - prefix parents and backslash ['(', ')', '\'] by backslash
   *   (escape character in postcript)
   */
  if (job->status->is_in_cut
      && (c != '\f' )
      && (c != '\n'))
    return;
  job->status->is_in_cut = false;

  /* Start a new line ? */
  if (job->status->start_line)
    {
      if (job->status->start_page)
	{
	  /* only if there is something to print! */
	  virtual_begin (job);
	  job->status->start_page = false ;
	  /* This is the first line of a new page, hence we need (page
	   * independance) to repeat the current font */
	  job->status->face = new_face;
	  job->status->face_declared = false;

	  if (job->numbering)
	    {
	      if (CURRENT_FILE (job)->lines % job->numbering == 0)
		output (jdiv, "(%zu) # (", CURRENT_FILE (job)->lines);
	      else
		output (jdiv, "0 T (");
	    }
	  else
	    output_char (jdiv, '(');
	}
      else
	{
	  /* This is a new line, but not the first in the page */
	  if (job->numbering)
	    {
	      if (CURRENT_FILE (job)->lines % job->numbering == 0)
		output (jdiv, "(%zu) # (", CURRENT_FILE (job)->lines);
	      else
		output (jdiv, "0 T (");
	    }
	  else
	    {
	      /* This is a new line, hence we can consider that there is
		 no need to close the current font: just consider it is
		 the new font, but not declared. */
	      output_char (jdiv, '(');
	      /* Why the hell did I do this? */
	      /* FIXME: This is suppresed because of the changes of encoding */
	      job->status->face_declared = (job->status->face_declared
					    && (job->status->face == new_face));
	      job->status->face = new_face;
	    }
	}
      job->status->start_line = false;
    }

  /*
   * Interpret each character
   *
   *  One of the tricky stuff is that we want to avoid uncessary
   * Changes of font.  For instance, I see no point in
   * Updating the font right before a \n.
   */
  switch (c) {
  case '\f':  		/* Form feed */
    if (!job->interpret)
      goto print;

    /* Close current line */
    if (!job->status->start_line)
      {
	end_of_line (job);
	job->status->start_line = true;
      }
    /* start a new page ? */
    if (job->status->start_page)
      {
	virtual_begin (job);
      }
    /* Close current page and begin another */
    virtual_end (job);
    job->status->start_page = true;
    break;

  case '\n':
  /* Now the primary eol is \n.  It is up to a2ps-prog to change the
   * \r or \n\r to \n
   * The program  */
    (CURRENT_FILE (job))->lines++;
    job->status->start_line = true;
    end_of_line (job);

    if (page_full) {
      virtual_end (job);
      job->status->start_page = true ;
    }
    break;

  case '\t':
    if (!job->interpret)
      goto print;

    /* Is this a new font? */
    if (job->status->face != new_face) {
      if (!job->status->face_declared)
	output (jdiv, ") %s\n(", face_eo_ps (job->status->face));
      else
	output (jdiv, ") S\n(");
      job->status->face = new_face;
      job->status->face_declared = false;
    }

    /* Tabs are interpreted  but we want to go to the same
     * column as if the font were Courier
     */
    job->status->column =
      (A2_MAX(job->status->wx / COURIER_WX, job->status->column)
       / job->tabsize + 1) * job->tabsize;
    job->status->wx = job->status->column * COURIER_WX;
    if (line_full) {
      if (job->folding) {
	fold_line (job, new_face);
      } else {
	job->status->is_in_cut = true;
	return;
      }
    }
    /* Make sure that the font is given */
    if (!job->status->face_declared) {
      output (jdiv, ") %s", face_eo_ps(job->status->face));
      job->status->face_declared = true;
    } else
      output (jdiv, ") S");
    output (jdiv, " %zu T (", job->status->column);
    break;
  print:
  default:
    {
      static unsigned char mb_flag = 0;
      char buf[256];
      unsigned nchars;
      *buf = '\0';

      /* Is this a new font? */
      if (job->status->face != new_face) {
        if (!job->status->face_declared)
          output (jdiv, ") %s\n(", face_eo_ps (job->status->face));
        else
          output (jdiv, ") S\n(");
        job->status->face = new_face;
        job->status->face_declared = false;
      }

      if ((unsigned char) c > 127 && encoding_get_composite_flag (job->encoding) &&
          job->status->face != Symbol) {
        if (mb_flag) {
          nchars = ps_escape_char (job, mb_flag, buf) + 
            ps_escape_char (job, (unsigned char) c, buf);
          job->status->wx += char_composite_WX(job);
          job->status->column += nchars;
          if (line_full) {
	    if (job->folding) {
              fold_line (job, new_face);
              job->status->column = nchars*2;
              job->status->wx = char_composite_WX(job);
	    } else {
	      job->status->is_in_cut = true;
	      return;
	    }
          }
          mb_flag = 0;
        } else {
          mb_flag = (unsigned char) c;
          return;
        }
      } else {
        nchars = ps_escape_char (job, (unsigned char) c, buf);
        job->status->wx += char_WX (job, (unsigned char) c);
        job->status->column += nchars;
        if (line_full) {
          if (job->folding) {
            fold_line (job, new_face);
            job->status->column = nchars;
            job->status->wx = char_WX (job, (unsigned char)c);
          } else {
            job->status->is_in_cut = true;
            return;
          }
        }
      }
      output (jdiv, "%s", buf);
      job->status->chars += nchars;
    }
    break;
  }
}

/*
 * Print the content of a C string \0 terminated
 */
void
ps_print_string (a2ps_job * job, char * string, enum face_e face)
{
  while (*string)
    ps_print_char (job, *string++, face);
}

/*
 * Print the N first chars in BUFFER
 */
void
ps_print_buffer (a2ps_job * job,
		 const char * buffer,
		 size_t start, size_t end,
		 enum face_e face)
{
  size_t i;
  for (i = start ; i < end ; i++)
    ps_print_char (job, buffer [i], face);
}

/* Handling the input sessions in an output session, i.e., typically
   the files. */

/* A new file will be printed. */

void
ps_begin_file (a2ps_job *job)
{
  /* Reinitialize the ps status */
  initialize_ps_status (job->status);

  /* Alignment is not needed for the first file. */
  if (job->jobs->len == 0)
    return;

  switch (job->file_align)
    {
    case file_align_virtual:
      /* Nothing to do. */
      break;

    case file_align_rank:
      /* Issue virtual until we are in a new rank. */
      {
	size_t alignment = job->madir == madir_rows ? job->columns : job->rows;
	while (job->pages % alignment != 0)
	  virtual_empty_output (job);
      }
      break;

    case file_align_page:
      /* End the page if needed. */
      page_flush (job);
      break;

    case file_align_sheet:
      /* End the sheet if needed. */
      sheet_flush (job);
      break;

    default:
      /* job->file_align is a number.  We must issue as many pages as
         needed to have a page number which is a multiple of
         FILE_ALIGN plus one. */
      page_flush (job);
      while ((job->sheets) % (size_t) job->file_align != 0)
	page_empty_output (job);
      break;
    }
}

void
ps_end_file (a2ps_job *job)
{
  /* If a line was not finished, close it.
   * Typically, no \n at end of file */
  if (!job->status->start_line)
    {
      if (!job->status->face_declared)
	output (jdiv, ") %s\n", face_eo_ps (job->status->face));
      else
	output (jdiv, ") S\n");
    }
  if (!job->status->start_page)
    virtual_end (job);

  /* Set the number of pages/sheets printed */
  file_job_synchronize_pages (job);
  file_job_synchronize_sheets (job);

  /* If we don't want to print binaries, complain and quit */
  check_binary_file (job);
}
