// --------------------------------------------------------------------
// Interface with Pdflatex
// --------------------------------------------------------------------
/*

    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 "ipevisitor.h"
#include "ipestyle.h"
#include "ipegroup.h"
#include "iperef.h"

#include "ipefontpool.h"
#include "ipelatex.h"

/*! \class IpeLatex
  \brief Object that converts latex source to PDF format.

  This object is responsible for creating the PDF representation of
  text objects.

*/

//! Create a converter object.
IpeLatex::IpeLatex()
{
  iFontPool = 0;
}

//! Destructor.
IpeLatex::~IpeLatex()
{
  for (XFormList::iterator it = iXForms.begin(); it != iXForms.end(); ++it)
    delete *it;
  delete iFontPool;
}

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

//! Return the newly created font pool and pass ownership of pool to caller.
IpeFontPool *IpeLatex::TakeFontPool()
{
  IpeFontPool *pool = iFontPool;
  iFontPool = 0;
  return pool;
}

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

class TextCollectingVisitor : public IpeVisitor {
public:
  TextCollectingVisitor(IpeLatex::TextList *list);
  virtual void VisitText(const IpeText *obj);
  virtual void VisitGroup(const IpeGroup *obj);
  virtual void VisitReference(const IpeReference *obj);
public:
  bool iTextFound;
private:
  IpeLatex::TextList *iList;
  IpeAttributeSeq iSize;
};

TextCollectingVisitor::TextCollectingVisitor(IpeLatex::TextList *list)
  : iList(list)
{
  iSize.push_back(IpeAttribute()); // null text size
}

void TextCollectingVisitor::VisitText(const IpeText *obj)
{
  IpeLatex::SText s;
  s.iText = obj;
  if (iSize.back())
    s.iSize =  iSize.back();
  else
    s.iSize = obj->Size();
  iList->push_back(s);
  iTextFound = true;
}

void TextCollectingVisitor::VisitGroup(const IpeGroup *obj)
{
  if (iSize.back())
    iSize.push_back(iSize.back());
  else
    iSize.push_back(obj->TextSize());
  for (IpeGroup::const_iterator it = obj->begin(); it != obj->end(); ++it)
    (*it)->Accept(*this);
  iSize.pop_back();
}

void TextCollectingVisitor::VisitReference(const IpeReference *obj)
{
  const IpeObject *ref = obj->Object();
  if (ref)
    ref->Accept(*this);
}

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

/*! Scan a page and insert all text objects into IpeLatex's list.
  Returns total number of text objects found so far. */
int IpeLatex::ScanPage(IpePage *page)
{
  TextCollectingVisitor visitor(&iTextObjects);
  for (IpePage::const_iterator it = page->begin(); it != page->end(); ++it) {
    visitor.iTextFound = false;
    visitor(it->Object());
    if (visitor.iTextFound)
      it->InvalidateBBox();
  }
  return iTextObjects.size();
}

/*! Create a Latex source file with all the text objects collected
  before.  The client should have prepared a directory for the
  Pdflatex run, and pass the name of the Latex source file to be
  written by IpeLatex.

  Returns the number of text objects that did not yet have an XForm,
  or a negative error code.
*/
int IpeLatex::CreateLatexSource(IpeStream &stream, IpeString preamble,
				const IpeStyleSheet *sheet)
{
  int count = 0;
  stream << "\\pdfcompresslevel0\n"
	 << "\\nonstopmode\n"
	 << "\\documentclass{article}\n"
	 << "\\newcommand{\\Ipechar}[1]{\\unichar{#1}}\n"
	 << "\\usepackage{color}\n";
  IpeAttributeSeq colors;
  sheet->AllNames(IpeAttribute::EColor, colors);
  for (IpeAttributeSeq::const_iterator it = colors.begin();
       it != colors.end(); ++it) {
    // only symbolic names (not black, white, void)
    IpeString name = sheet->Repository()->String(*it);
    IpeColor value = sheet->Repository()->ToColor(sheet->Find(*it));
    if (value.IsGray())
      stream << "\\definecolor{" << name << "}{gray}{"
	     << value.iRed << "}\n";
    else
      stream << "\\definecolor{" << name << "}{rgb}{"
	     << value.iRed << "," << value.iGreen << ","
	     << value.iBlue << "}\n";
  }
  stream << sheet->TotalPreamble() << "\n"
	 << preamble << "\n"
	 << "\\pagestyle{empty}\n"
	 << "\\newcount\\bigpoint\\dimen0=0.01bp\\bigpoint=\\dimen0\n"
	 << "\\begin{document}\n"
	 << "\\begin{picture}(500,500)\n";
  for (TextList::iterator it = iTextObjects.begin();
       it != iTextObjects.end(); ++it) {
    const IpeText *text = it->iText;

    if (!text->GetXForm())
      count++;

    // skip objects with null or unknown symbolic size
    IpeAttribute fsAttr = sheet->Find(it->iSize);
    if (!fsAttr)
      continue;

    // compute x-stretch factor from textstretch
    IpeVector stretch(1.0, 1.0);
    if (it->iSize.IsSymbolic()) {
      IpeAttribute ts(IpeAttribute::ETextStretch, true, it->iSize.Index());
      IpeAttribute tsAttr = sheet->Find(ts);
      if (tsAttr)
	stretch = sheet->Repository()->ToVector(tsAttr);
    }

    IpeAttribute color = text->Stroke();
    IpeAttribute abs = sheet->Find(color);
    // invisible text objects: run with black color
    if (color.IsNullOrVoid() || abs.IsNull())
      abs = IpeAttribute::Black();
    IpeColor col = sheet->Repository()->ToColor(abs);

    stream << "\\setbox0=\\hbox{";
    char ipeid[20];  // on 64-bit systems, pointers are 64 bit
    std::sprintf(ipeid, "/%08lx", (unsigned long int)(text));
    if (text->IsMiniPage()) {
      stream << "\\begin{minipage}{" << text->Width()/stretch.iX << "bp}";
    }

    if (fsAttr.IsNumeric()) {
      IpeFixed fs = fsAttr.Number();
      stream << "\\fontsize{" << fs << "}"
	     << "{" << fs.Mult(6, 5) << "bp}\\selectfont\n";
    } else
      stream << sheet->Repository()->String(fsAttr) << "\n";
    stream << "\\color[rgb]{"
	   << col.iRed << "," << col.iGreen << ","
	   << col.iBlue << "}%\n";
    IpeString txt = text->Text();
    for (int i = 0; i < txt.size(); ) {
      int uc = txt.unicode(i); // advances i
      if (uc < 0x80)
	stream << char(uc);
      else
	stream << "\\Ipechar{" << uc << "}";
    }
    if (text->IsMiniPage())
      stream << "\\end{minipage}";
    stream << "}\n"
	   << "\\count0=\\dp0\\divide\\count0 by \\bigpoint\n"
	   << "\\pdfxform attr{/IpeId " << ipeid
	   << " /IpeStretch [ " << stretch.iX << " " << stretch.iY << " ] "
      	   << " /IpeDepth \\the\\count0}"
	   << "0\\put(0,0){\\pdfrefxform\\pdflastxform}\n";
  }
  stream << "\\end{picture}\n\\end{document}\n";
  return count;
}

bool IpeLatex::GetXForm(const IpePdfObj *xform)
{
  const IpePdfDict *dict = xform->Dict();
  if (!dict || dict->Stream().size() == 0)
    return false;
  IpeText::XForm *xf = new IpeText::XForm;
  iXForms.push_back(xf);
  assert(!dict->Deflated());
  xf->iStream = dict->Stream();
  /* Should we check /Matrix and /FormType?
     /Type /XObject
     /Subtype /Form
     /IpeId /abcd1234
     /IpeDepth 246
     /IpeStretch [ 3 3 ]
     /BBox [0 0 4.639 4.289]
     /FormType 1
     /Matrix [1 0 0 1 0 0]
     /Resources 11 0 R
  */
  // Get Ipe id
  const IpePdfObj *id = dict->Get("IpeId", &iPdf);
  if (!id || !id->Name())
    return false;
  IpeLex lex(id->Name()->Value());
  xf->iRefCount = lex.GetHexNumber(); // abusing refcount field
  const IpePdfObj *depth = dict->Get("IpeDepth", &iPdf);
  if (!depth || !depth->Number())
    return false;
  xf->iDepth = int(depth->Number()->Value());
  const IpePdfObj *stretch = dict->Get("IpeStretch", &iPdf);
  if (!stretch || !stretch->Array())
    return false;
  const IpePdfObj *xstretch = stretch->Array()->Obj(0, &iPdf);
  const IpePdfObj *ystretch = stretch->Array()->Obj(1, &iPdf);
  if (!xstretch || !xstretch->Number() ||
      !ystretch || !ystretch->Number())
     return false;
  xf->iStretch = IpeVector(xstretch->Number()->Value(),
			   ystretch->Number()->Value());
  // Get BBox
  const IpePdfObj *bbox = dict->Get("BBox", &iPdf);
  if (!bbox || !bbox->Array())
    return false;
  const IpePdfObj *a[4];
  for (int i = 0; i < 4; i++) {
    a[i] = bbox->Array()->Obj(i, &iPdf);
    if (!a[i] || !a[i]->Number())
      return false;
  }
  IpeVector bl(a[0]->Number()->Value(), a[1]->Number()->Value());
  IpeVector sz(a[2]->Number()->Value(), a[3]->Number()->Value());
  xf->iBBox.AddPoint(bl);
  xf->iBBox.AddPoint(bl + sz);
  if (xf->iBBox.Min() != IpeVector::Zero) {
    ipeDebug("PDF bounding box is not zero-aligned: (%g, %g)",
	     xf->iBBox.Min().iX, xf->iBBox.Min().iY);
    return false;
  }
  const IpePdfObj *res = dict->Get("Resources", &iPdf);
  if (!res || !res->Dict()) {
    Warn("No /Resources in XForm.");
    return false;
  }
  /*
    /Font << /F8 9 0 R /F10 18 0 R >>
    /ProcSet [ /PDF /Text ]
  */
  const IpePdfObj *fontDict = res->Dict()->Get("Font", &iPdf);
  if (fontDict && fontDict->Dict()) {
    int numFonts = fontDict->Dict()->Count();
    xf->iFonts.resize(numFonts);
    for (int i = 0; i < numFonts; i++) {
      IpeString fontName(fontDict->Dict()->Key(i));
      if (fontName[0] != 'F') {
	Warn(IpeString("Weird font name: ") + fontName);
	return false;
      }
      int fontNumber = IpeLex(fontName.substr(1)).GetInt();
      xf->iFonts[i] = fontNumber;
      const IpePdfObj *fontRef = fontDict->Dict()->Get(fontName, 0);
      if (!fontRef || !fontRef->Ref()) {
	Warn("Font is not indirect");
	return false;
      }
      iFontObjects[fontNumber] = fontRef->Ref()->Value();
    }
  }
  return true;
}

bool IpeLatex::GetEmbeddedFont(int fno, int objno)
{
  const IpePdfObj *fontObj = iPdf.Object(objno);
  /*
    /Type /Font
    /Subtype /Type1
    /Encoding 24 0 R
    /FirstChar 6
    /LastChar 49
    /Widths 25 0 R
    /BaseFont /YEHLEP+CMR10
    /FontDescriptor 7 0 R
  */
  if (!fontObj || !fontObj->Dict())
    return false;
  const IpePdfObj *type = fontObj->Dict()->Get("Type", 0);
  if (!type || !type->Name() || type->Name()->Value() != "Font")
    return false;

  // Check whether the stream data is already there.
  IpeFontPool::iterator it = iFontPool->begin();
  while (it != iFontPool->end() && it->iLatexNumber != fno)
    ++it;
  if (it == iFontPool->end()) {
    iFontPool->push_back(IpeFont());
    it = iFontPool->end() - 1;
  }
  IpeFont &font = *it;
  font.iLatexNumber = fno;

  // get font dictionary
  for (int i = 0; i < fontObj->Dict()->Count(); i++) {
    IpeString key(fontObj->Dict()->Key(i));
    if (key != "FontDescriptor") {
      const IpePdfObj *data = fontObj->Dict()->Get(key, &iPdf);
      font.iFontDict += IpeString("/") + key + " " + data->Repr() + "\n";
    }
  }

  // Get type
  const IpePdfObj *subtype = fontObj->Dict()->Get("Subtype", &iPdf);
  if (!subtype || !subtype->Name())
    return false;
  if (subtype->Name()->Value() == "Type1")
    font.iType = IpeFont::EType1;
  else if (subtype->Name()->Value() == "TrueType")
    font.iType = IpeFont::ETrueType;
  else {
    Warn("Pdflatex has embedded a font of a type not supported by Ipe.");
    return false;
  }

  // Get encoding vector
  if (font.iType == IpeFont::EType1) {
    const IpePdfObj *enc = fontObj->Dict()->Get("Encoding", &iPdf);
    if (!enc || !enc->Dict())
      return 0;
    const IpePdfObj *diff = enc->Dict()->Get("Differences", &iPdf);
    if (!diff || !diff->Array())
      return 0;
    for (int i = 0; i < 0x100; ++i)
      font.iEncoding[i] = ".notdef";
    int idx = 0;
    for (int i = 0; i < diff->Array()->Count(); ++i) {
      const IpePdfObj *obj = diff->Array()->Obj(i, 0);
      if (obj->Number())
	idx = int(obj->Number()->Value());
      else if (obj->Name() && idx < 0x100)
	font.iEncoding[idx++] = obj->Name()->Value();
    }
  }

  // Get name
  const IpePdfObj *name = fontObj->Dict()->Get("BaseFont", 0);
  if (!name || !name->Name())
    return false;
  font.iName = name->Name()->Value();

  // Get widths
  const IpePdfObj *fc = fontObj->Dict()->Get("FirstChar", 0);
  const IpePdfObj *wid = fontObj->Dict()->Get("Widths", &iPdf);
  if (font.iType == IpeFont::EType1 && fc == 0 && wid == 0) {
    font.iStandardFont = true;
    return true;
  }
  font.iStandardFont = false;
  if (!fc || !fc->Number() || !wid || !wid->Array())
    return false;
  int firstChar = int(fc->Number()->Value());
  for (int i = 0; i < wid->Array()->Count(); ++i) {
    const IpePdfObj *obj = wid->Array()->Obj(i, 0);
    if (!obj->Number())
      return false;
    font.iWidth[firstChar + i] = int(obj->Number()->Value());
  }

#if 0
  ipeDebug("Font resource /F%d = %s", fno, font.iFontDict.CString());
#endif
  // get font descriptor
  /*
    /Ascent 694
    /CapHeight 683
    /Descent -194
    /FontName /YEHLEP+CMR10
    /ItalicAngle 0
    /StemV 69
    /XHeight 431
    /FontBBox [-251 -250 1009 969]
    /Flags 4
    /CharSet (/Sigma/one)
    /FontFile 8 0 R
  */
  const IpePdfObj *fontDescriptor =
    fontObj->Dict()->Get("FontDescriptor", &iPdf);
  if (!fontDescriptor && font.iStandardFont)
    return true;  // it's one of the 14 base fonts, no more data needed
  if (!fontDescriptor->Dict())
    return false;
  const IpePdfObj *fontFile = 0;
  IpeString fontFileKey;
  for (int i = 0; i < fontDescriptor->Dict()->Count(); i++) {
    IpeString key(fontDescriptor->Dict()->Key(i));
    if (key.size() >= 8 && key.substr(0, 8) == "FontFile") {
      fontFileKey = key;
      fontFile = fontDescriptor->Dict()->Get(key, &iPdf);
    } else {
      const IpePdfObj *data = fontDescriptor->Dict()->Get(key, &iPdf);
      font.iFontDescriptor += IpeString("/") + key + " " + data->Repr() + "\n";
    }
  }
  font.iFontDescriptor += IpeString("/") + fontFileKey + " ";
#if 0
  ipeDebug("Font descriptor /F%d = %s", fno,
	   font.iFontDescriptor.CString());
#endif
  // get embedded font file
  if (!fontFile || !fontFile->Dict() || fontFile->Dict()->Stream().size() == 0)
    return false;
  assert(font.iStreamData.size() == 0);
  assert(!fontFile->Dict()->Deflated());
  font.iStreamData = fontFile->Dict()->Stream();
  font.iLength1 = font.iLength2 = font.iLength3 = 0;
  for (int i = 0; i < fontFile->Dict()->Count(); i++) {
    IpeString key = fontFile->Dict()->Key(i);
    const IpePdfObj *data = fontFile->Dict()->Get(key, &iPdf);
    if (key != "Length")
      font.iStreamDict += IpeString("/") + key + " " + data->Repr() + "\n";
    if (key == "Length1" && data->Number())
      font.iLength1 = int(data->Number()->Value());
    if (key == "Length2" && data->Number())
      font.iLength2 = int(data->Number()->Value());
    if (key == "Length3" && data->Number())
      font.iLength3 = int(data->Number()->Value());
  }
  if (font.iType == IpeFont::EType1 &&
      (!font.iLength1 || !font.iLength2 || !font.iLength3))
    return false;
  return true;
}

//! Read the PDF file created by Pdflatex.
/*! Must have performed the call to Pdflatex, and pass the name of the
  resulting output file.
*/
bool IpeLatex::ReadPdf(IpeDataSource &source)
{
  if (!iPdf.Parse(source)) {
    Warn("Ipe cannot parse the PDF file produced by Pdflatex.");
    return false;
  }

  const IpePdfDict *page1 = iPdf.Page();
  const IpePdfObj *res = page1->Get("Resources", &iPdf);
  if (!res || !res->Dict())
    return false;

  iFontPool = new IpeFontPool;

  const IpePdfObj *obj = res->Dict()->Get("XObject", &iPdf);
  if (!obj || !obj->Dict()) {
    Warn("Page 1 has no XForms.");
    return false;
  }
  // collect list of XObject's and their fonts
  for (int i = 0; i < obj->Dict()->Count(); i++) {
    IpeString key = obj->Dict()->Key(i);
    const IpePdfObj *xform = obj->Dict()->Get(key, &iPdf);
    if (!GetXForm(xform))
      return false;
  }
  // collect all fonts
  std::map<int, int>::iterator it;
  for (it = iFontObjects.begin(); it != iFontObjects.end(); ++it) {
    int fno = it->first;
    int objno = it->second;
    if (!GetEmbeddedFont(fno, objno))
      return false;
  }
  return true;
}

//! Notify all text objects about their updated PDF code.
/*! Returns true if successful. */
bool IpeLatex::UpdateTextObjects()
{
  for (TextList::iterator it = iTextObjects.begin();
       it != iTextObjects.end(); ++it) {
    XFormList::iterator xf = iXForms.begin();
    unsigned long int ipeid = (unsigned long int) it->iText;
    while (xf != iXForms.end() && (*xf)->iRefCount != ipeid)
      ++xf;
    if (xf == iXForms.end())
      return false;
    IpeText::XForm *xform = *xf;
    iXForms.erase(xf);
    it->iText->SetXForm(xform);
  }
  return true;
}

/*! Messages about the (mis)behaviour of Pdflatex, probably
  incomprehensible to the user. */
void IpeLatex::Warn(IpeString msg)
{
  ipeDebug(msg.CString());
}

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