//========================================================================
//
// PSOutputDev.cc
//
// Copyright 1996 Derek B. Noonburg
//
//========================================================================

#ifdef __GNUC__
#pragma implementation
#endif

#include <stdio.h>
#include <stddef.h>
#include <stdarg.h>
#include <signal.h>
#include <math.h>
#include "GString.h"
#include "config.h"
#include "Object.h"
#include "Error.h"
#include "GfxState.h"
#include "GfxFont.h"
#include "FontFile.h"
#include "Catalog.h"
#include "Page.h"
#include "Stream.h"
#include "PSOutputDev.h"

//------------------------------------------------------------------------
// Parameters
//------------------------------------------------------------------------

// Generate Level 1 PostScript?
GBool psOutLevel1 = gFalse;

int paperWidth = defPaperWidth;
int paperHeight = defPaperHeight;

//------------------------------------------------------------------------
// PostScript prolog and setup
//------------------------------------------------------------------------

static char *prolog[] = {
  "/xpdf 75 dict def xpdf begin",
  "% PDF special state",
  "/pdfDictSize 14 def",
  "/pdfSetup {",
  "  2 array astore",
  "  /setpagedevice where {",
  "    pop 3 dict dup begin",
  "      exch /PageSize exch def",
  "      /ImagingBBox null def",
  "      /Policies 1 dict dup begin /PageSize 3 def end def",
  "    end setpagedevice",
  "  } {",
  "    pop",
  "  } ifelse",
  "} def",
  "/pdfStartPage {",
  "  pdfDictSize dict begin",
  "  /pdfFill [0] def",
  "  /pdfStroke [0] def",
  "  /pdfLastFill false def",
  "  /pdfLastStroke false def",
  "  /pdfTextMat [1 0 0 1 0 0] def",
  "  /pdfFontSize 0 def",
  "  /pdfCharSpacing 0 def",
  "  /pdfTextRender 0 def",
  "  /pdfTextRise 0 def",
  "  /pdfWordSpacing 0 def",
  "  /pdfHorizScaling 1 def",
  "} def",
  "/pdfEndPage { end } def",
  "/sCol { pdfLastStroke not {",
  "          pdfStroke aload length",
  "          1 eq { setgray } { setrgbcolor} ifelse",
  "          /pdfLastStroke true def /pdfLastFill false def",
  "        } if } def",
  "/fCol { pdfLastFill not {",
  "          pdfFill aload length",
  "          1 eq { setgray } { setrgbcolor } ifelse",
  "          /pdfLastFill true def /pdfLastStroke false def",
  "        } if } def",
  "% build a font",
  "/pdfMakeFont {",
  "  3 2 roll findfont",
  "  3 2 roll 1 matrix scale makefont",
  "  dup length dict begin",
  "    { 1 index /FID ne { def } { pop pop } ifelse } forall",
  "    /Encoding exch def",
  "    currentdict",
  "  end",
  "  definefont pop",
  "} def",
  "% graphics state operators",
  "/q { gsave pdfDictSize dict begin } def",
  "/Q { end grestore } def",
  "/cm { concat } def",
  "/d { setdash } def",
  "/i { setflat } def",
  "/j { setlinejoin } def",
  "/J { setlinecap } def",
  "/M { setmiterlimit } def",
  "/w { setlinewidth } def",
  "% color operators",
  "/g { dup 1 array astore /pdfFill exch def setgray",
  "    /pdfLastFill true def /pdfLastStroke false def } def",
  "/G { dup 1 array astore /pdfStroke exch def setgray",
  "     /pdfLastStroke true def /pdfLastFill false def } def",
  "/rg { 3 copy 3 array astore /pdfFill exch def setrgbcolor",
  "     /pdfLastFill true def /pdfLastStroke false def } def",
  "/RG { 3 copy 3 array astore /pdfStroke exch def setrgbcolor",
  "     /pdfLastStroke true def /pdfLastFill false def } def",
  "% path segment operators",
  "/m { moveto } def",
  "/l { lineto } def",
  "/c { curveto } def",
  "/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto",
  "      neg 0 rlineto closepath } def",
  "% path painting operators",
  "/S { sCol stroke } def",
  "/f { fCol fill } def",
  "/f* { fCol eofill } def",
  "% clipping operators",
  "/W { clip newpath } def",
  "/W* { eoclip newpath } def",
  "% text state operators",
  "/Tc { /pdfCharSpacing exch def } def",
  "/Tf { dup /pdfFontSize exch def",
  "      dup pdfHorizScaling mul exch matrix scale",
  "      pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put",
  "      exch findfont exch makefont setfont } def",
  "/Tr { /pdfTextRender exch def } def",
  "/Ts { /pdfTextRise exch def } def",
  "/Tw { /pdfWordSpacing exch def } def",
  "/Tz { /pdfHorizScaling exch def } def",
  "% text positioning operators",
  "/Td { pdfTextMat transform moveto } def",
  "/Tm { /pdfTextMat exch def } def",
  "% text string operators",
  "/Tj { pdfTextRender 1 and 0 eq { fCol } { sCol } ifelse",
  "      0 pdfTextRise pdfTextMat dtransform rmoveto",
  "      pdfFontSize mul pdfHorizScaling mul",
  "      1 index stringwidth pdfTextMat idtransform pop",
  "      sub 1 index length dup 0 ne { div } { pop pop 0 } ifelse",
  "      pdfWordSpacing 0 pdfTextMat dtransform 32",
  "      4 3 roll pdfCharSpacing add 0 pdfTextMat dtransform",
  "      6 5 roll awidthshow",
  "      0 pdfTextRise neg pdfTextMat dtransform rmoveto } def",
  "/TJm { pdfFontSize 0.001 mul mul neg 0",
  "       pdfTextMat dtransform rmoveto } def",
  "% Level 1 image operators",
  "/pdfIm1 {",
  "  /pdfImBuf1 4 index string def",
  "  { currentfile pdfImBuf1 readhexstring pop } image",
  "} def",
  "/pdfImM1 {",
  "  /pdfImBuf1 4 index 7 add 8 idiv string def",
  "  { currentfile pdfImBuf1 readhexstring pop } imagemask",
  "} def",
  "% Level 2 image operators",
  "/pdfImBuf 100 string def",
  "/pdfIm {",
  "  image",
  "  { currentfile pdfImBuf readline",
  "    not { pop exit } if",
  "    (%-EOD-) eq { exit } if } loop",
  "} def",
  "/pdfImM {",
  "  fCol imagemask",
  "  { currentfile pdfImBuf readline",
  "    not { pop exit } if",
  "    (%-EOD-) eq { exit } if } loop",
  "} def",
  "end",
  NULL
};

//------------------------------------------------------------------------
// Fonts
//------------------------------------------------------------------------

struct PSFont {
  char *name;			// PDF name
  char *psName;			// PostScript name
};

struct PSSubstFont {
  char *psName;			// PostScript name
  double mWidth;		// width of 'm' character
};

static PSFont psFonts[] = {
  {"Courier",               "Courier"},
  {"Courier-Bold",          "Courier-Bold"},
  {"Courier-Oblique",       "Courier-Bold"},
  {"Courier-BoldOblique",   "Courier-BoldOblique"},
  {"Helvetica",             "Helvetica"},
  {"Helvetica-Bold",        "Helvetica-Bold"},
  {"Helvetica-Oblique",     "Helvetica-Oblique"},
  {"Helvetica-BoldOblique", "Helvetica-BoldOblique"},
  {"Symbol",                "Symbol"},
  {"Times-Roman",           "Times-Roman"},
  {"Times-Bold",            "Times-Bold"},
  {"Times-Italic",          "Times-Italic"},
  {"Times-BoldItalic",      "Times-BoldItalic"},
  {"ZapfDingbats",          "ZapfDingbats"},
  {NULL}
};

static PSSubstFont psSubstFonts[] = {
  {"Helvetica",             0.833},
  {"Helvetica-Oblique",     0.833},
  {"Helvetica-Bold",        0.889},
  {"Helvetica-BoldOblique", 0.889},
  {"Times-Roman",           0.788},
  {"Times-Italic",          0.722},
  {"Times-Bold",            0.833},
  {"Times-BoldItalic",      0.778},
  {"Courier",               0.600},
  {"Courier-Oblique",       0.600},
  {"Courier-Bold",          0.600},
  {"Courier-BoldOblique",   0.600}
};

//------------------------------------------------------------------------
// PSOutputDev
//------------------------------------------------------------------------

PSOutputDev::PSOutputDev(char *fileName, Catalog *catalog,
			 int firstPage, int lastPage,
			 GBool embedType11, GBool doForm1) {
  Page *page;
  Dict *resDict;
  char **p;
  int pg;

  // initialize
  embedType1 = embedType11;
  doForm = doForm1;
  fontIDs = NULL;
  fontFileIDs = NULL;
  fontFileNames = NULL;
  f = NULL;
  if (doForm)
    lastPage = firstPage;

  // open file or pipe
  ok = gTrue;
  if (!strcmp(fileName, "-")) {
    fileType = psStdout;
    f = stdout;
  } else if (fileName[0] == '|') {
    fileType = psPipe;
#ifdef HAVE_POPEN
#ifndef WIN32
    signal(SIGPIPE, (void (*)(int))SIG_IGN);
#endif
    if (!(f = popen(fileName + 1, "w"))) {
      error(-1, "Couldn't run print command '%s'", fileName);
      ok = gFalse;
      return;
    }
#else
    error(-1, "Print commands are not supported ('%s')", fileName);
    ok = gFalse;
    return;
#endif
  } else {
    fileType = psFile;
    if (!(f = fopen(fileName, "w"))) {
      error(-1, "Couldn't open PostScript file '%s'", fileName);
      ok = gFalse;
      return;
    }
  }

  // initialize fontIDs, fontFileIDs, and fontFileNames lists
  fontIDSize = 64;
  fontIDLen = 0;
  fontIDs = (Ref *)gmalloc(fontIDSize * sizeof(Ref));
  fontFileIDSize = 64;
  fontFileIDLen = 0;
  fontFileIDs = (Ref *)gmalloc(fontFileIDSize * sizeof(Ref));
  fontFileNameSize = 64;
  fontFileNameLen = 0;
  fontFileNames = (char **)gmalloc(fontFileNameSize * sizeof(char *));

  // write header
  if (doForm) {
    writePS("%%!PS-Adobe-3.0 Resource-Form\n");
    writePS("%%%%Creator: xpdf/pdftops %s\n", xpdfVersion);
    writePS("%%%%EndComments\n");
  } else {
    writePS("%%!PS-Adobe-3.0\n");
    writePS("%%%%Creator: xpdf/pdftops %s\n", xpdfVersion);
    writePS("%%%%Pages: %d\n", lastPage - firstPage + 1);
    writePS("%%%%EndComments\n");
  }

  // write prolog
  if (!doForm)
    writePS("%%%%BeginProlog\n");
  writePS("%%%%BeginResource: xpdf %s\n", xpdfVersion);
  for (p = prolog; *p; ++p)
    writePS("%s\n", *p);
  writePS("%%%%EndResource\n");
  if (!doForm)
    writePS("%%%%EndProlog\n");

  // set up fonts
  if (!doForm)
    writePS("%%%%BeginSetup\n");
  writePS("xpdf begin\n");
  for (pg = firstPage; pg <= lastPage; ++pg) {
    if ((resDict = catalog->getPage(pg)->getResourceDict()))
      setupFonts(resDict);
  }
  if (doForm) {
    writePS("end\n");
  } else {
    writePS("%d %d pdfSetup\n", paperWidth, paperHeight);
    writePS("%%%%EndSetup\n");
  }

  // write form header
  if (doForm) {
    page = catalog->getPage(firstPage);
    writePS("4 dict dup begin\n");
    writePS("/BBox [%d %d %d %d] def\n",
	    (int)page->getX1(), (int)page->getY1(),
	    (int)page->getX2(), (int)page->getY2());
    writePS("/FormType 1 def\n");
    writePS("/Matrix [1 0 0 1 0 0] def\n");
  }

  // initialize sequential page number
  seqPage = 1;
}

PSOutputDev::~PSOutputDev() {
  if (f) {
    if (doForm) {
      writePS("end\n");
      writePS("/Foo exch /Form defineresource pop\n");
    } else {
      writePS("%%%%Trailer\n");
      writePS("end\n");
      writePS("%%%%EOF\n");
    }
    if (fileType == psFile) {
      fclose(f);
    }
#ifdef HAVE_POPEN
    else if (fileType == psPipe) {
      pclose(f);
#ifndef WIN32
      signal(SIGPIPE, (void (*)(int))SIG_DFL);
#endif
    }
#endif
  }
  if (fontIDs)
    gfree(fontIDs);
  if (fontFileIDs)
    gfree(fontFileIDs);
  if (fontFileNames)
    gfree(fontFileNames);
}

void PSOutputDev::setupFonts(Dict *resDict) {
  Object fontDict, xObjDict, xObj, resObj;
  GfxFontDict *gfxFontDict;
  GfxFont *font;
  int i;

  resDict->lookup("Font", &fontDict);
  if (fontDict.isDict()) {
    gfxFontDict = new GfxFontDict(fontDict.getDict());
    for (i = 0; i < gfxFontDict->getNumFonts(); ++i) {
      font = gfxFontDict->getFont(i);
      setupFont(font);
    }
    delete gfxFontDict;
  }
  fontDict.free();

  resDict->lookup("XObject", &xObjDict);
  if (xObjDict.isDict()) {
    for (i = 0; i < xObjDict.dictGetLength(); ++i) {
      xObjDict.dictGetVal(i, &xObj);
      if (xObj.isStream()) {
	xObj.streamGetDict()->lookup("Resources", &resObj);
	if (resObj.isDict())
	  setupFonts(resObj.getDict());
	resObj.free();
      }
      xObj.free();
    }
  }
  xObjDict.free();
}

void PSOutputDev::setupFont(GfxFont *font) {
  Ref fontFileID;
  GString *name;
  char *psName;
  char *charName;
  double scale;
  int i, j;

  // check if font is already set up
  for (i = 0; i < fontIDLen; ++i) {
    if (fontIDs[i].num == font->getID().num &&
	fontIDs[i].gen == font->getID().gen)
      return;
  }

  // add entry to fontIDs list
  if (fontIDLen >= fontIDSize) {
    fontIDSize += 64;
    fontIDs = (Ref *)grealloc(fontIDs, fontIDSize * sizeof(Ref));
  }
  fontIDs[fontIDLen++] = font->getID();

  // check for embedded Type 1 font
  if (embedType1 && font->getType() == fontType1 &&
      font->getEmbeddedFontID(&fontFileID)) {
    setupEmbeddedType1Font(&fontFileID);
    psName = font->getEmbeddedFontName();
    scale = 1;

  // check for external Type 1 font file
  } else if (embedType1 && font->getType() == fontType1 &&
	     font->getExtFontFile()) {
    setupEmbeddedType1Font(font->getExtFontFile());
    // this assumes that the PS font name matches the PDF font name
    psName = font->getName()->getCString();
    scale = 1;

  // check for embedded Type 1C font
  } else if (embedType1 && font->getType() == fontType1C &&
	     font->getEmbeddedFontID(&fontFileID)) {
    setupEmbeddedType1CFont(font, &fontFileID);
    psName = font->getEmbeddedFontName();
    scale = 1;

  // do font substitution
  } else {
    name = font->getName();
    psName = NULL;
    scale = 1.0;
    if (name) {
      for (i = 0; psFonts[i].name; ++i) {
	if (name->cmp(psFonts[i].name) == 0) {
	  psName = psFonts[i].psName;
	  break;
	}
      }
    }
    if (!psName) {
      if (font->isFixedWidth())
	i = 8;
      else if (font->isSerif())
	i = 4;
      else
	i = 0;
      if (font->isBold())
	i += 2;
      if (font->isItalic())
	i += 1;
      psName = psSubstFonts[i].psName;
      scale = font->getWidth('m') / psSubstFonts[i].mWidth;
      if (scale < 0.1)
	scale = 1;
    }
  }

  // generate PostScript code to set up the font
  writePS("/F%d_%d /%s %g\n",
	  font->getID().num, font->getID().gen, psName, scale);
  for (i = 0; i < 256; i += 8) {
    writePS((i == 0) ? "[ " : "  ");
    for (j = 0; j < 8; ++j) {
      charName = font->getCharName(i+j);
      writePS("/%s", charName ? charName : ".notdef");
    }
    writePS((i == 256-8) ? "]\n" : "\n");
  }
  writePS("pdfMakeFont\n");
}

void PSOutputDev::setupEmbeddedType1Font(Ref *id) {
  static char hexChar[17] = "0123456789abcdef";
  Object refObj, strObj, obj1, obj2;
  Dict *dict;
  int length1, length2;
  int c;
  int start[4];
  GBool binMode;
  int i;

  // check if font is already embedded
  for (i = 0; i < fontFileIDLen; ++i) {
    if (fontFileIDs[i].num == id->num &&
	fontFileIDs[i].gen == id->gen)
      return;
  }

  // add entry to fontFileIDs list
  if (fontFileIDLen >= fontFileIDSize) {
    fontFileIDSize += 64;
    fontFileIDs = (Ref *)grealloc(fontFileIDs, fontFileIDSize * sizeof(Ref));
  }
  fontFileIDs[fontFileIDLen++] = *id;

  // get the font stream and info
  refObj.initRef(id->num, id->gen);
  refObj.fetch(&strObj);
  refObj.free();
  if (!strObj.isStream()) {
    error(-1, "Embedded font file object is not a stream");
    goto err1;
  }
  if (!(dict = strObj.streamGetDict())) {
    error(-1, "Embedded font stream is missing its dictionary");
    goto err1;
  }
  dict->lookup("Length1", &obj1);
  dict->lookup("Length2", &obj2);
  if (!obj1.isInt() || !obj2.isInt()) {
    error(-1, "Missing length fields in embedded font stream dictionary");
    obj1.free();
    obj2.free();
    goto err1;
  }
  length1 = obj1.getInt();
  length2 = obj2.getInt();
  obj1.free();
  obj2.free();

  // copy ASCII portion of font
  strObj.streamReset();
  for (i = 0; i < length1 && (c = strObj.streamGetChar()) != EOF; ++i)
    fputc(c, f);

  // figure out if encrypted portion is binary or ASCII
  binMode = gFalse;
  for (i = 0; i < 4; ++i) {
    start[i] = strObj.streamGetChar();
    if (start[i] == EOF) {
      error(-1, "Unexpected end of file in embedded font stream");
      goto err1;
    }
    if (!((start[i] >= '0' && start[i] <= '9') ||
	  (start[i] >= 'A' && start[i] <= 'F') ||
	  (start[i] >= 'a' && start[i] <= 'f')))
      binMode = gTrue;
  }

  // convert binary data to ASCII
  if (binMode) {
    for (i = 0; i < 4; ++i) {
      fputc(hexChar[(start[i] >> 4) & 0x0f], f);
      fputc(hexChar[start[i] & 0x0f], f);
    }
    while (i < length2) {
      if ((c = strObj.streamGetChar()) == EOF)
	break;
      fputc(hexChar[(c >> 4) & 0x0f], f);
      fputc(hexChar[c & 0x0f], f);
      if (++i % 32 == 0)
	fputc('\n', f);
    }
    if (i % 32 > 0)
      fputc('\n', f);

  // already in ASCII format -- just copy it
  } else {
    for (i = 0; i < 4; ++i)
      fputc(start[i], f);
    for (i = 4; i < length2; ++i) {
      if ((c = strObj.streamGetChar()) == EOF)
	break;
      fputc(c, f);
    }
  }

  // write padding and "cleartomark"
  for (i = 0; i < 8; ++i)
    writePS("00000000000000000000000000000000"
	    "00000000000000000000000000000000\n");
  writePS("cleartomark\n");

 err1:
  strObj.free();
}

//~ This doesn't handle .pfb files or binary eexec data (which only
//~ happens in pfb files?).
void PSOutputDev::setupEmbeddedType1Font(char *fileName) {
  FILE *fontFile;
  int c;
  int i;

  // check if font is already embedded
  for (i = 0; i < fontFileNameLen; ++i) {
    if (!strcmp(fontFileNames[i], fileName))
      return;
  }

  // add entry to fontFileNames list
  if (fontFileNameLen >= fontFileNameSize) {
    fontFileNameSize += 64;
    fontFileNames = (char **)grealloc(fontFileNames,
				      fontFileNameSize * sizeof(char *));
  }
  fontFileNames[fontFileNameLen++] = fileName;

  // copy the font file
  if (!(fontFile = fopen(fileName, "rb"))) {
    error(-1, "Couldn't open external font file");
    return;
  }
  while ((c = fgetc(fontFile)) != EOF)
    fputc(c, f);
  fclose(fontFile);
}

void PSOutputDev::setupEmbeddedType1CFont(GfxFont *font, Ref *id) {
  char *fontBuf;
  int fontLen;
  Type1CFontConverter *cvt;
  int i;

  // check if font is already embedded
  for (i = 0; i < fontFileIDLen; ++i) {
    if (fontFileIDs[i].num == id->num &&
	fontFileIDs[i].gen == id->gen)
      return;
  }

  // add entry to fontFileIDs list
  if (fontFileIDLen >= fontFileIDSize) {
    fontFileIDSize += 64;
    fontFileIDs = (Ref *)grealloc(fontFileIDs, fontFileIDSize * sizeof(Ref));
  }
  fontFileIDs[fontFileIDLen++] = *id;

  // convert it to a Type 1 font
  fontBuf = font->readEmbFontFile(&fontLen);
  cvt = new Type1CFontConverter(fontBuf, fontLen, f);
  cvt->convert();
  delete cvt;
  gfree(fontBuf);
}

void PSOutputDev::startPage(int pageNum, GfxState *state) {
  int x1, y1, x2, y2, width, height, t;
  double xScale, yScale;

  if (doForm) {

    writePS("/PaintProc {\n");
    writePS("begin xpdf begin\n");
    writePS("pdfSetup\n");

  } else {

    writePS("%%%%Page: %d %d\n", pageNum, seqPage);
    writePS("%%%%BeginPageSetup\n");

    // rotate, translate, and scale page
    x1 = (int)(state->getX1() + 0.5);
    y1 = (int)(state->getY1() + 0.5);
    x2 = (int)(state->getX2() + 0.5);
    y2 = (int)(state->getY2() + 0.5);
    width = x2 - x1;
    height = y2 - y1;
    if (width > height) {
      writePS("%%%%PageOrientation: Landscape\n");
      writePS("pdfStartPage\n");
      writePS("90 rotate\n");
      writePS("%d %d translate\n", -x1, -(y1 + paperWidth));
      t = width;
      width = height;
      height = t;
    } else {
      writePS("%%%%PageOrientation: Portrait\n");
      writePS("pdfStartPage\n");
      if (x1 != 0 || y1 != 0)
	writePS("%d %d translate\n", -x1, -y1);
    }
    if (width > paperWidth || height > paperHeight) {
      xScale = (double)paperWidth / (double)width;
      yScale = (double)paperHeight / (double)height;
      if (yScale < xScale)
	xScale = yScale;
      writePS("%0.4f %0.4f scale\n", xScale, xScale);
    }

    writePS("%%%%EndPageSetup\n");
    ++seqPage;
  }
}

void PSOutputDev::endPage() {
  if (doForm) {
    writePS("pdfEndPage\n");
    writePS("end end\n");
    writePS("} def\n");
  } else {
    writePS("showpage\n");
    writePS("%%%%PageTrailer\n");
    writePS("pdfEndPage\n");
  }
}

void PSOutputDev::saveState(GfxState *state) {
  writePS("q\n");
}

void PSOutputDev::restoreState(GfxState *state) {
  writePS("Q\n");
}

void PSOutputDev::updateCTM(GfxState *state, double m11, double m12,
			    double m21, double m22, double m31, double m32) {
  writePS("[%g %g %g %g %g %g] cm\n", m11, m12, m21, m22, m31, m32);
}

void PSOutputDev::updateLineDash(GfxState *state) {
  double *dash;
  double start;
  int length, i;

  state->getLineDash(&dash, &length, &start);
  writePS("[");
  for (i = 0; i < length; ++i)
    writePS("%g%s", dash[i], (i == length-1) ? "" : " ");
  writePS("] %g d\n", start);
}

void PSOutputDev::updateFlatness(GfxState *state) {
  writePS("%d i\n", state->getFlatness());
}

void PSOutputDev::updateLineJoin(GfxState *state) {
  writePS("%d j\n", state->getLineJoin());
}

void PSOutputDev::updateLineCap(GfxState *state) {
  writePS("%d J\n", state->getLineCap());
}

void PSOutputDev::updateMiterLimit(GfxState *state) {
  writePS("%g M\n", state->getMiterLimit());
}

void PSOutputDev::updateLineWidth(GfxState *state) {
  writePS("%g w\n", state->getLineWidth());
}

void PSOutputDev::updateFillColor(GfxState *state) {
  GfxColor *color;
  double r, g, b;

  color = state->getFillColor();
  r = color->getR();
  g = color->getG();
  b = color->getB();
  if (r == g && g == b)
    writePS("%g g\n", r);
  else
    writePS("%g %g %g rg\n", r, g, b);
}

void PSOutputDev::updateStrokeColor(GfxState *state) {
  GfxColor *color;
  double r, g, b;

  color = state->getStrokeColor();
  r = color->getR();
  g = color->getG();
  b = color->getB();
  if (r == g && g == b)
    writePS("%g G\n", r);
  else
    writePS("%g %g %g RG\n", r, g, b);
}

void PSOutputDev::updateFont(GfxState *state) {
  if (state->getFont()) {
    writePS("/F%d_%d %g Tf\n",
	    state->getFont()->getID().num, state->getFont()->getID().gen,
	    state->getFontSize());
  }
}

void PSOutputDev::updateTextMat(GfxState *state) {
  double *mat;

  mat = state->getTextMat();
  writePS("[%g %g %g %g %g %g] Tm\n",
	  mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
}

void PSOutputDev::updateCharSpace(GfxState *state) {
  writePS("%g Tc\n", state->getCharSpace());
}

void PSOutputDev::updateRender(GfxState *state) {
  writePS("%d Tr\n", state->getRender());
}

void PSOutputDev::updateRise(GfxState *state) {
  writePS("%g Ts\n", state->getRise());
}

void PSOutputDev::updateWordSpace(GfxState *state) {
  writePS("%g Tw\n", state->getWordSpace());
}

void PSOutputDev::updateHorizScaling(GfxState *state) {
  writePS("%g Tz\n", state->getHorizScaling());
}

void PSOutputDev::updateTextPos(GfxState *state) {
  writePS("%g %g Td\n", state->getLineX(), state->getLineY());
}

void PSOutputDev::updateTextShift(GfxState *state, double shift) {
  writePS("%g TJm\n", shift);
}

void PSOutputDev::stroke(GfxState *state) {
  doPath(state->getPath());
  writePS("S\n");
}

void PSOutputDev::fill(GfxState *state) {
  doPath(state->getPath());
  writePS("f\n");
}

void PSOutputDev::eoFill(GfxState *state) {
  doPath(state->getPath());
  writePS("f*\n");
}

void PSOutputDev::clip(GfxState *state) {
  doPath(state->getPath());
  writePS("W\n");
}

void PSOutputDev::eoClip(GfxState *state) {
  doPath(state->getPath());
  writePS("W*\n");
}

void PSOutputDev::doPath(GfxPath *path) {
  GfxSubpath *subpath;
  double x0, y0, x1, y1, x2, y2, x3, y3, x4, y4;
  int n, m, i, j;

  n = path->getNumSubpaths();

  if (n == 1 && path->getSubpath(0)->getNumPoints() == 5) {
    subpath = path->getSubpath(0);
    x0 = subpath->getX(0);
    y0 = subpath->getY(0);
    x4 = subpath->getX(4);
    y4 = subpath->getY(4);
    if (x4 == x0 && y4 == y0) {
      x1 = subpath->getX(1);
      y1 = subpath->getY(1);
      x2 = subpath->getX(2);
      y2 = subpath->getY(2);
      x3 = subpath->getX(3);
      y3 = subpath->getY(3);
      if (x0 == x1 && x2 == x3 && y0 == y3 && y1 == y2) {
	writePS("%g %g %g %g re\n",
		x0 < x2 ? x0 : x2, y0 < y1 ? y0 : y1,
		fabs(x2 - x0), fabs(y1 - y0));
	return;
      } else if (x0 == x3 && x1 == x2 && y0 == y1 && y2 == y3) {
	writePS("%g %g %g %g re\n",
		x0 < x1 ? x0 : x1, y0 < y2 ? y0 : y2,
		fabs(x1 - x0), fabs(y2 - y0));
	return;
      }
    }
  }

  for (i = 0; i < n; ++i) {
    subpath = path->getSubpath(i);
    m = subpath->getNumPoints();
    writePS("%g %g m\n", subpath->getX(0), subpath->getY(0));
    j = 1;
    while (j < m) {
      if (subpath->getCurve(j)) {
	writePS("%g %g %g %g %g %g c\n", subpath->getX(j), subpath->getY(j),
		subpath->getX(j+1), subpath->getY(j+1),
		subpath->getX(j+2), subpath->getY(j+2));
	j += 3;
      } else {
	writePS("%g %g l\n", subpath->getX(j), subpath->getY(j));
	++j;
      }
    }
  }
}

void PSOutputDev::drawString(GfxState *state, GString *s) {
  // check for invisible text -- this is used by Acrobat Capture
  if ((state->getRender() & 3) == 3)
    return;

  writePSString(s);
  writePS(" %g Tj\n", state->getFont()->getWidth(s));
}

void PSOutputDev::drawImageMask(GfxState *state, Stream *str,
				int width, int height, GBool invert,
				GBool inlineImg) {
  int len;

  len = height * ((width + 7) / 8);
  if (psOutLevel1)
    doImageL1(NULL, invert, inlineImg, str, width, height, len);
  else
    doImage(NULL, invert, inlineImg, str, width, height, len);
}

void PSOutputDev::drawImage(GfxState *state, Stream *str, int width,
			    int height, GfxImageColorMap *colorMap,
			    GBool inlineImg) {
  int len;

  len = height * ((width * colorMap->getNumPixelComps() *
		   colorMap->getBits() + 7) / 8);
  if (psOutLevel1)
    doImageL1(colorMap, gFalse, inlineImg, str, width, height, len);
  else
    doImage(colorMap, gFalse, inlineImg, str, width, height, len);
}

void PSOutputDev::doImageL1(GfxImageColorMap *colorMap,
			    GBool invert, GBool inlineImg,
			    Stream *str, int width, int height, int len) {
  ImageStream *imgStr;
  Guchar pixBuf[4];
  GfxColor color;
  int x, y, i;

  // width, height, matrix, bits per component
  if (colorMap) {
    writePS("%d %d 8 [%d 0 0 %d 0 %d] pdfIm1\n",
	    width, height,
	    width, -height, height);
  } else {
    writePS("%d %d %s [%d 0 0 %d 0 %d] pdfImM1\n",
	    width, height, invert ? "true" : "false",
	    width, -height, height);
  }

  // image
  if (colorMap) {

    // set up to process the data stream
    imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
			     colorMap->getBits());
    imgStr->reset();

    // process the data stream
    i = 0;
    for (y = 0; y < height; ++y) {

      // write the line
      for (x = 0; x < width; ++x) {
	imgStr->getPixel(pixBuf);
	colorMap->getColor(pixBuf, &color);
	fprintf(f, "%02x", (int)(color.getGray() * 255 + 0.5));
	if (++i == 32) {
	  fputc('\n', f);
	  i = 0;
	}
      }
    }
    if (i != 0)
      fputc('\n', f);
    delete imgStr;

  // imagemask
  } else {
    str->reset();
    i = 0;
    for (y = 0; y < height; ++y) {
      for (x = 0; x < width; x += 8) {
	fprintf(f, "%02x", str->getChar() & 0xff);
	if (++i == 32) {
	  fputc('\n', f);
	  i = 0;
	}
      }
    }
    if (i != 0)
      fputc('\n', f);
  }
}

void PSOutputDev::doImage(GfxImageColorMap *colorMap,
			  GBool invert, GBool inlineImg,
			  Stream *str, int width, int height, int len) {
  GfxColorSpace *colorSpace;
  GString *s;
  int n, numComps;
  Guchar *color;
  GBool useRLE, useA85;
  int c;
  int i, j, k;

  // color space
  if (colorMap) {
    colorSpace = colorMap->getColorSpace();
    if (colorSpace->isIndexed())
      writePS("[/Indexed ");
    switch (colorSpace->getMode()) {
    case colorGray:
      writePS("/DeviceGray ");
      break;
    case colorCMYK:
      writePS("/DeviceCMYK ");
      break;
    case colorRGB:
      writePS("/DeviceRGB ");
      break;
    }
    if (colorSpace->isIndexed()) {
      n = colorSpace->getIndexHigh();
      numComps = colorSpace->getNumColorComps();
      writePS("%d <\n", n);
      for (i = 0; i <= n; i += 8) {
	writePS("  ");
	for (j = i; j < i+8 && j <= n; ++j) {
	  color = colorSpace->getLookupVal(j);
	  for (k = 0; k < numComps; ++k)
	    writePS("%02x", color[k]);
	}
	writePS("\n");
      }
      writePS("> ] setcolorspace\n");
    } else {
      writePS("setcolorspace\n");
    }
  }

  // image dictionary
  writePS("<<\n  /ImageType 1\n");

  // width, height, matrix, bits per component
  writePS("  /Width %d\n", width);
  writePS("  /Height %d\n", height);
  writePS("  /ImageMatrix [%d 0 0 %d 0 %d]\n", width, -height, height);
  writePS("  /BitsPerComponent %d\n",
	  colorMap ? colorMap->getBits() : 1);

  // decode 
  if (colorMap) {
    writePS("  /Decode [");
    numComps = colorMap->getNumPixelComps();
    for (i = 0; i < numComps; ++i) {
      if (i > 0)
	writePS(" ");
      writePS("%g %g", colorMap->getDecodeLow(i), colorMap->getDecodeHigh(i));
    }
    writePS("]\n");
  } else {
    writePS("  /Decode [%d %d]\n", invert ? 1 : 0, invert ? 0 : 1);
  }

  if (doForm) {

    // data source
    writePS("  /DataSource <~\n");

    // write image data stream, using ASCII85 encode filter
    str = new ASCII85Encoder(str);
    str->reset();
    while ((c = str->getChar()) != EOF)
      fputc(c, f);
    fputc('\n', f);
    delete str;

    // end of image dictionary
    writePS(">>\n%s\n", colorMap ? "image" : "imagemask");

  } else {

    // data source
    writePS("  /DataSource currentfile\n");
    s = str->getPSFilter("    ");
    if (inlineImg || !s) {
      useRLE = gTrue;
      useA85 = gTrue;
    } else {
      useRLE = gFalse;
      useA85 = str->isBinary();
    }
    if (useA85)
      writePS("    /ASCII85Decode filter\n");
    if (useRLE)
      writePS("    /RunLengthDecode filter\n");
    else
      writePS("%s", s->getCString());
    if (s)
      delete s;

    // end of image dictionary
    writePS(">>\n%s\n", colorMap ? "pdfIm" : "pdfImM");

    // write image data stream

    // cut off inline image streams at appropriate length
    if (inlineImg)
      str = new FixedLengthEncoder(str, len);
    else if (!useRLE)
      str = str->getBaseStream();

    // add RunLengthEncode and ASCII85 encode filters
    if (useRLE)
      str = new RunLengthEncoder(str);
    if (useA85)
      str = new ASCII85Encoder(str);

    // copy the stream data
    str->reset();
    while ((c = str->getChar()) != EOF)
      fputc(c, f);

    // add newline and trailer to the end
    fputc('\n', f);
    fputs("%-EOD-\n", f);

    // delete encoders
    if (useRLE || useA85)
      delete str;
  }
}

void PSOutputDev::writePS(char *fmt, ...) {
  va_list args;

  va_start(args, fmt);
  vfprintf(f, fmt, args);
  va_end(args);
}

void PSOutputDev::writePSString(GString *s) {
  Guchar *p;
  int n;

  fputc('(', f);
  for (p = (Guchar *)s->getCString(), n = s->getLength(); n; ++p, --n) {
    if (*p == '(' || *p == ')' || *p == '\\')
      fprintf(f, "\\%c", *p);
    else if (*p < 0x20 || *p >= 0x80)
      fprintf(f, "\\%03o", *p);
    else
      fputc(*p, f);
  }
  fputc(')', f);
}
