// --------------------------------------------------------------------
// Creating Postscript output
// --------------------------------------------------------------------
/*

    This file is part of the extensible drawing editor Ipe.
    Copyright (C) 1993-2004  Otfried Cheong

    Ipe 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 2 of the License, or
    (at your option) any later version.

    As a special exception, you have permission to link Ipe with the
    CGAL library and distribute executables, as long as you follow the
    requirements of the Gnu General Public License in regard to all of
    the software in the executable aside from CGAL.

    Ipe 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 Ipe; if not, you can find it at
    "http://www.gnu.org/copyleft/gpl.html", or write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include "ipeimage.h"
#include "ipetext.h"
#include "ipevisitor.h"
#include "ipepainter.h"
#include "ipegroup.h"
#include "iperef.h"
#include "ipeutils.h"
#include "ipedoc.h"

#include "ipepswriter.h"
#include "ipefontpool.h"

static const char hexChar[17] = "0123456789abcdef";

// --------------------------------------------------------------------

class IpePsPainter : public IpePainter {
public:
  IpePsPainter(const IpeStyleSheet *style, IpeStream &stream);
  virtual ~IpePsPainter() { }

  virtual void Rect(const IpeRect &re);

protected:
  virtual void DoPush();
  virtual void DoPop();
  virtual void DoNewPath();
  virtual void DoMoveTo(const IpeVector &v);
  virtual void DoLineTo(const IpeVector &v);
  virtual void DoCurveTo(const IpeVector &v1, const IpeVector &v2,
			 const IpeVector &v3);
  virtual void DoClosePath();
  virtual void DoDrawPath();
  virtual void DoDrawBitmap(IpeBitmap bitmap);
  virtual void DoDrawText(const IpeText *text);

private:
  void DrawColor(IpeAttribute color);
  void StrokePath();
  void FillPath(bool eoFill, bool preservePath);

private:
  IpeStream &iStream;
  // iActiveState records the attribute settings that have been
  // recorded in the Postscript output, to avoid repeating them
  // over and over again.
  StateSeq iActiveState;
  int iImageNumber;
};

IpePsPainter::IpePsPainter(const IpeStyleSheet *style, IpeStream &stream)
  : IpePainter(style), iStream(stream)
{
  State state;
  // Postscript has only one current color:
  // we use iStroke for that
  state.iStroke = IpeAttribute::Black();
  state.iDashStyle = IpeAttribute::Solid();
  state.iLineCap = IpeAttribute(IpeAttribute::ELineCap, false, 0);
  state.iLineJoin = IpeAttribute(IpeAttribute::ELineJoin, false, 0);
  iActiveState.push_back(state);
  iImageNumber = 1;
}

void IpePsPainter::Rect(const IpeRect &re)
{
  iStream << Matrix() * re.Min() << " "
	  << Matrix().Linear() * (re.Max() - re.Min()) << " re\n";
}

void IpePsPainter::DoNewPath()
{
  iStream << "np\n";
}

void IpePsPainter::DoMoveTo(const IpeVector &v)
{
  iStream << Matrix() * v << " m\n";
}

void IpePsPainter::DoLineTo(const IpeVector &v)
{
  iStream << Matrix() * v << " l\n";
}

void IpePsPainter::DoCurveTo(const IpeVector &v1, const IpeVector &v2,
			      const IpeVector &v3)
{
  iStream << Matrix() * v1 << " "
	  << Matrix() * v2 << " "
	  << Matrix() * v3 << " c\n";
}

void IpePsPainter::DoClosePath()
{
  iStream << "h ";
}

void IpePsPainter::DoPush()
{
  State state = iActiveState.back();
  iActiveState.push_back(state);
  // don't need the outermost push/pop
  // (it's only to preserve outer transformation for "Untransform"
  // if (iState.size() > 2) // have already pushed
  iStream << "q ";
}

void IpePsPainter::DoPop()
{
  iActiveState.pop_back();
  //if (iState.size() > 1) // have already popped
  iStream << "Q\n";
}

void IpePsPainter::DrawColor(IpeAttribute color)
{
  if (color.IsNullOrVoid())
    return;
  assert(color.IsAbsolute());
  IpeColor col = StyleSheet()->Repository()->ToColor(color);
  if (col.IsGray())
    iStream << col.iRed << " g ";
  else
    iStream << col << " rg ";
}

void IpePsPainter::StrokePath()
{
  State &s = iState.back();
  State &sa = iActiveState.back();
  const IpeRepository *rep = StyleSheet()->Repository();
  if (s.iDashStyle && s.iDashStyle != sa.iDashStyle) {
    sa.iDashStyle = s.iDashStyle;
    if (s.iDashStyle.IsSolid())
      iStream << "[] 0 d ";
    else
      iStream << rep->String(s.iDashStyle) << " d ";
  }
  if (s.iLineWidth && s.iLineWidth != sa.iLineWidth) {
    sa.iLineWidth = s.iLineWidth;
    iStream << rep->String(s.iLineWidth) << " w ";
  }
  if (s.iLineCap && s.iLineCap != sa.iLineCap) {
    sa.iLineCap = s.iLineCap;
    iStream << int(s.iLineCap.Index()) << " J ";
  }
  if (s.iLineJoin && s.iLineJoin != sa.iLineJoin) {
    sa.iLineJoin = s.iLineJoin;
    iStream << int(s.iLineJoin.Index()) << " j ";
  }
  if (s.iStroke != sa.iStroke) {
    sa.iStroke = s.iStroke;
    DrawColor(s.iStroke);
  }
  iStream << "S";
}

void IpePsPainter::FillPath(bool eoFill, bool preservePath)
{
  State &s = iState.back();
  State &sa = iActiveState.back();
  if (s.iFill != sa.iStroke) {
    sa.iStroke = s.iFill;
    DrawColor(s.iFill);
  }
  if (preservePath)
    iStream << "q ";
  iStream << (eoFill ? "f*" : "f");
  if (preservePath)
    iStream << " Q ";
}

void IpePsPainter::DoDrawPath()
{
  bool noStroke = Stroke().IsNullOrVoid();
  bool noFill = Fill().IsNullOrVoid();
  bool eoFill = !WindRule() || !WindRule().Index();
  if (noStroke && noFill) {
    iStream << "np";  // flush path
  } else if (noStroke) {
    FillPath(eoFill, false);
  } else if (noFill) {
    StrokePath();
  } else {
    FillPath(eoFill, true);
    StrokePath();
  }
  iStream << "\n";
}

void IpePsPainter::DoDrawBitmap(IpeBitmap bitmap)
{
  switch (bitmap.ColorSpace()) {
  case IpeBitmap::EDeviceGray:
    iStream << "/DeviceGray setcolorspace ";
    break;
  case IpeBitmap::EDeviceRGB:
    iStream << "/DeviceRGB setcolorspace ";
    break;
  case IpeBitmap::EDeviceCMYK:
    iStream << "/DeviceCMYK setcolorspace ";
    break;
  }
  iStream << Matrix() << " cm\n";
  iStream << "<< /ImageType 1\n";
  iStream << "   /Width " << bitmap.Width() << "\n";
  iStream << "   /Height " << bitmap.Height() << "\n";
  iStream << "   /BitsPerComponent " << bitmap.BitsPerComponent() << "\n";
  iStream << "   /Decode [ ";
  for (int i = 0; i < bitmap.Components(); ++i)
    iStream << "0 1 ";
  iStream << "]\n";
  iStream << "   /ImageMatrix [ " << bitmap.Width() << " 0 0 "
	  << -bitmap.Height() << " 0 " << bitmap.Height() << " ]\n";
  iStream << "   /DataSource currentfile /ASCII85Decode filter";
  if (bitmap.Filter() == IpeBitmap::EFlateDecode)
    iStream << " /FlateDecode filter\n";
  else if (bitmap.Filter() == IpeBitmap::EDCTDecode)
    iStream << " /DCTDecode filter\n";
  else
    iStream << "\n";
  iStream << ">>\n";
  iStream << "%%BeginIpeImage: " << iImageNumber << " "
	  << bitmap.Size() << "\n";
  bitmap.SetObjNum(iImageNumber++);
  iStream << "image\n";
  const char *p = bitmap.Data();
  const char *p1 = p + bitmap.Size();
  IpeA85Stream a85(iStream);
  while (p < p1)
    a85.PutChar(*p++);
  a85.Close();
  iStream << "%%EndIpeImage\n";
}

void IpePsPainter::DoDrawText(const IpeText *text)
{
  const IpeText::XForm *xf = text->GetXForm();
  if (xf) {
    iStream << Matrix() << " cm " ;
    iStream << IpeMatrix(xf->iStretch.iX, 0, 0, xf->iStretch.iY, 0, 0)
	    << " cm ";
    // remove whitespace at the end
    const char *data = xf->iStream.data();
    const char *p = data + xf->iStream.size() - 1;
    while (p >= data && (*p == ' ' || *p == '\n'))
      --p;
    if (p >= data)
      iStream.PutRaw(data, p - data + 1);
    iStream << "\n";
  }
}

// --------------------------------------------------------------------

/*! \class IpePsWriter
  \brief Create Postscript file.

  This class is responsible for the creation of a Postscript file from the
  Ipe data. You have to create an IpePsWriter first, providing a stream
  that has been opened for (binary) writing and is empty.

*/

//! Create Postscript writer operating on this (open and empty) file.
IpePsWriter::IpePsWriter(IpeTellStream &stream, const IpeDocument *doc,
			 bool eps)
  : iStream(stream), iDoc(doc), iEps(eps)
{
  iOrdinal = 1; // first page will be ordinal 1
}

// --------------------------------------------------------------------

//! Create the document header and prolog (the resources).
/*! Embeds no fonts if \c pool is 0.
  Returns false if a TrueType font is present. */
bool IpePsWriter::CreateHeader(IpeString creator)
{
  const IpeFontPool *pool = iDoc->FontPool();
  const IpeDocument::SProperties &props = iDoc->Properties();

  // Check needed and supplied fonts, and reject Truetype fonts
  IpeString needed = "";
  IpeString neededSep = "%%DocumentNeededResources: font ";
  IpeString supplied = "";
  IpeString suppliedSep = "%%DocumentSuppliedResources: font ";
  if (pool) {
    for (IpeFontPool::const_iterator font = pool->begin();
	 font != pool->end(); ++font) {
      if (font->iType == IpeFont::ETrueType)
	return false;
      if (font->iStandardFont) {
	needed += neededSep;
	needed += font->iName;
	needed += "\n";
	neededSep = "%%+ font ";
      } else {
	supplied += suppliedSep;
	supplied += font->iName;
	supplied += "\n";
	suppliedSep = "%%+ font ";
      }
    }
  }

  if (iEps)
    iStream << "%!PS-Adobe-3.0 EPSF-3.0\n";
  else
    iStream << "%!PS-Adobe-3.0\n";

  iStream << "%%Creator: Ipelib " << IPELIB_VERSION;
  if (!creator.empty())
    iStream << " (" << creator << ")";
  iStream << "\n";
  // CreationDate is informational, no fixed format
  iStream << "%%CreationDate: " << props.iModified << "\n";
  // Should use level 1 if no images present?
  iStream << "%%LanguageLevel: 2\n";
  // If bounding box is used for non-EPS files, it should
  // be a bounding box for all pages overlaid
  if (iEps) {
    const IpePage *page = (*iDoc)[0];
    std::vector<bool> layers;
    page->MakeLayerTable(layers, 0, false);
    IpeBBoxPainter painter(iDoc->StyleSheet());
    for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it) {
      if (layers[it->Layer()])
	it->Object()->Draw(painter);
    }
    IpeRect r = painter.BBox();
    iStream << "%%BoundingBox: " // (x0, y0) (x1, y1)
	    << int(r.Min().iX) << " " << int(r.Min().iY) << " "
	    << int(r.Max().iX + 1) << " " << int(r.Max().iY + 1) << "\n";
    iStream << "%%HiResBoundingBox: " << r.Min() << " " << r.Max() << "\n";
  } else {
    IpeRect m = iDoc->Properties().iMedia;
    iStream << "%%DocumentMedia: Plain "
	    << int(m.Width()) << " " << int(m.Height()) << " 0 () ()\n";
    iStream << "%%Pages: " << iDoc->TotalViews() << "\n";
  }
  iStream << needed;
  iStream << supplied;
  if (props.iAuthor.size())
    iStream << "%%Author: " << props.iAuthor << "\n";
  if (props.iTitle.size())
    iStream << "%%Title: " << props.iTitle << "\n";
  iStream << "%%EndComments\n";
  iStream << "%%BeginProlog\n";
  // procset <name> <version (a real)> <revision (an integer)>
  // <revision> is upwards compatible, <version> not
  iStream << "%%BeginResource: procset ipe 6.0 " << IPELIB_VERSION << "\n";
  iStream << "/ipe 40 dict def ipe begin\n";
  iStream << "/np { newpath } def\n";
  iStream << "/m { moveto } def\n";
  iStream << "/l { lineto } def\n";
  iStream << "/c { curveto } def\n";
  iStream << "/h { closepath } def\n";
  iStream << "/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto\n";
  iStream << "      neg 0 rlineto closepath } def\n";
  iStream << "/d { setdash } def\n";
  iStream << "/w { setlinewidth } def\n";
  iStream << "/J { setlinecap } def\n";
  iStream << "/j { setlinejoin } def\n";
  iStream << "/cm { [ 7 1 roll ] concat } def\n";
  iStream << "/q { gsave } def\n";
  iStream << "/Q { grestore } def\n";
  iStream << "/g { setgray } def\n";
  iStream << "/rg { setrgbcolor } def\n";
  iStream << "/G { setgray } def\n";  // used inside text objects
  iStream << "/RG { setrgbcolor } def\n"; // used inside text objects
  iStream << "/S { stroke } def\n";
  iStream << "/f* { eofill } def\n";
  iStream << "/f { fill } def\n";
  iStream << "/ipeMakeFont {\n";
  iStream << "  exch findfont\n";
  iStream << "  dup length dict begin\n";
  iStream << "    { 1 index /FID ne { def } { pop pop } ifelse } forall\n";
  iStream << "    /Encoding exch def\n";
  iStream << "    currentdict\n";
  iStream << "  end\n";
  iStream << "  definefont pop\n";
  iStream << "} def\n";
  iStream << "/ipeFontSize 0 def\n";
  iStream << "/Tf { dup /ipeFontSize exch store selectfont } def\n";
  iStream << "/Td { translate } def\n";
  iStream << "/BT { gsave } def\n";
  iStream << "/ET { grestore } def\n";
  iStream << "/TJ { 0 0 moveto { dup type /stringtype eq\n";
  iStream << " { show } { ipeFontSize mul -0.001 mul 0 rmoveto } ifelse\n";
  iStream << "} forall } def\n";
  iStream << "end\n";
  iStream << "%%EndResource\n";

  iStream << "%%EndProlog\n";
  iStream << "%%BeginSetup\n";
  iStream << "ipe begin\n";

  if (pool) {
    for (IpeFontPool::const_iterator font = pool->begin();
	 font != pool->end(); ++font) {

      if (font->iStandardFont) {
	iStream << "%%IncludeResource: font " << font->iName << "\n";
      } else {
	iStream << "%%BeginResource: font " << font->iName << "\n";
	EmbedFont(*font);
	iStream << "%%EndResource\n";
      }

      // Create font dictionary
      iStream << "/F" << font->iLatexNumber
	      << " /" << font->iName;
      IpeString sep = "\n[ ";
      for (int i = 0; i < 0x100; ++i) {
	if (i % 8 == 0) {
	  iStream << sep;
	  sep = "\n  ";
	}
	iStream << "/" << font->iEncoding[i];
      }
      iStream << " ]\nipeMakeFont\n";
    }
  }

  iStream << "%%EndSetup\n";
  return true;
}

//! Destructor.
IpePsWriter::~IpePsWriter()
{
  // nothing
}

//! Write all fonts to the Postscript file.
void IpePsWriter::EmbedFont(const IpeFont &font)
{
  // write unencrypted front matter
  iStream.PutRaw(font.iStreamData.data(), font.iLength1);
  // write encrypted section
  const char *p = font.iStreamData.data() + font.iLength1;
  const char *p1 = font.iStreamData.data() + font.iLength1 + font.iLength2;
  int i = 0;
  while (p < p1) {
    iStream.PutChar(hexChar[(*p >> 4) & 0x0f]);
    iStream.PutChar(hexChar[*p & 0x0f]);
    ++p;
    if (++i % 32 == 0)
      iStream << "\n";
  }
  if (i % 32 > 0)
    iStream << "\n";
  // write tail matter
  iStream.PutRaw(p1, font.iLength3);
}

// --------------------------------------------------------------------

//! Create contents and page stream for this page view.
void IpePsWriter::CreatePageView(int pno, int vno)
{
  const IpePage *page = (*iDoc)[pno];
  if (!iEps) {
    if (page->CountViews() > 1)
      iStream << "%%Page: " << (pno + 1) << "-" << (vno + 1)
	      << " " << iOrdinal++ << "\n";
    else
      iStream << "%%Page: " << (pno + 1) << " " << iOrdinal++ << "\n";
  }
  // Create page stream
  IpePsPainter painter(iDoc->StyleSheet(), iStream);
  std::vector<bool> layers;
  page->MakeLayerTable(layers, vno, false);
  for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it) {
    if (layers[it->Layer()])
      it->Object()->Draw(painter);
  }
  iStream << "showpage\n";
}

//! Save all pages in Postscript format.
void IpePsWriter::CreatePages(bool lastView)
{
  for (uint page = 0; page < iDoc->size(); ++page) {
    int nViews = (*iDoc)[page]->CountViews();
    if (lastView)
      CreatePageView(page, lastView - 1);
    else
      for (int view = 0; view < nViews; ++view)
	CreatePageView(page, view);
  }
}

//! Create the trailer of the Postscsript file.
void IpePsWriter::CreateTrailer()
{
  iStream << "%%Trailer\n";
  iStream << "end\n"; // to end ipe dictionary
  iStream << "%%EOF\n";
}

// --------------------------------------------------------------------

class IpePercentStream : public IpeStream {
public:
  IpePercentStream(IpeStream &stream) : iStream(stream), iNeedPercent(true) { /* nothing */ }
  virtual void PutChar(char ch);

private:
  IpeStream &iStream;
  bool iNeedPercent;
};

void IpePercentStream::PutChar(char ch)
{
  if (iNeedPercent)
    iStream.PutChar('%');
  iStream.PutChar(ch);
  iNeedPercent = (ch == '\n');
}

//! Save Ipe information in XML format.
void IpePsWriter::CreateXml(IpeString creator, int compressLevel)
{
  IpePercentStream s1(iStream);
  if (compressLevel > 0) {
    iStream << "%%BeginIpeXml: /FlateDecode\n";
    IpeA85Stream a85(s1);
    IpeDeflateStream s2(a85, compressLevel);
    iDoc->SaveAsXml(s2, creator, true);
    s2.Close();
  } else {
    iStream << "%%BeginIpeXml:\n";
    iDoc->SaveAsXml(s1, creator, true);
    s1.Close();
  }
  iStream << "%%EndIpeXml\n";
}

// --------------------------------------------------------------------

