// --------------------------------------------------------------------
// Canvas Painter
// --------------------------------------------------------------------
/*

    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 "ipetext.h"
#include "ipepdfparser.h"

#include "ipecanvas.h"
#include "ipefonts.h"

#include <qpainter.h>
#include <qpointarray.h>
#include <qimage.h>
#include <qpaintdevice.h>
#include <qapplication.h>

inline QPoint QPt(const IpeVector &v)
{
  return QPoint(int(v.iX), int(v.iY));
}

inline IpeString IpeQ(const QString &str)
{
  return IpeString(str.utf8());
}

inline QString QIpe(const IpeString &str)
{
  return QString::fromUtf8(str.CString());
}

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

class IpeRenderData : public IpeBitmap::MRenderData {
public:
  virtual ~IpeRenderData() { /* nothing */ }
  QImage iImage;
};

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

IpeCanvasPainter::IpeCanvasPainter(const IpeStyleSheet *sheet,
				   IpeCanvasFonts *fonts,
				   QPixmap *pixmap, QPainter *painter,
				   double zoom, bool pretty,
				   int maxBitmapSize)
  : IpePainter(sheet), iFonts(fonts),
    iPixmap(pixmap), iPainter(painter), iZoom(zoom), iPretty(pretty),
    iMaxBitmapSize(maxBitmapSize)
{
  iDimmed = false;
  iPainter->setFont(QApplication::font());
  iFont = 0;
  // these symbolic dash styles are on fixed indices
  for (int i = 0; i < 4; ++i)
    iDash[i] =
      StyleSheet()->Find(IpeAttribute(IpeAttribute::EDashStyle, true, i+1));
}

void IpeCanvasPainter::DoMoveTo(const IpeVector &v)
{
  if (iType.size() > 0 && iType.back() != EEndClosedPath)
    iType.push_back(EEndPath);
  iV.push_back(QPt(Matrix() * v));
  iType.push_back(EMoveTo);
}

void IpeCanvasPainter::DoLineTo(const IpeVector &v)
{
  iV.push_back(QPt(Matrix() * v));
  iType.push_back(ELineTo);
}

void IpeCanvasPainter::DoCurveTo(const IpeVector &v1, const IpeVector &v2,
				 const IpeVector &v3)
{
  iV.push_back(QPt(Matrix() * v1));
  iV.push_back(QPt(Matrix() * v2));
  iV.push_back(QPt(Matrix() * v3));
  iType.push_back(ECurveTo);
}

void IpeCanvasPainter::DoClosePath()
{
  iType.push_back(EEndClosedPath);
}

void IpeCanvasPainter::DoDrawPath()
{
  if (iType.size() > 0 && iType.back() != EEndClosedPath)
    iType.push_back(EEndPath);

  const IpeRepository *rep = StyleSheet()->Repository();
  // draw interior
  IpeAttribute fill = Fill();
  if (!fill.IsNullOrVoid()) {
    // fill color must be absolute
    assert(fill.IsAbsolute());
    IpeColor fillColor = rep->ToColor(fill);
    QColor qfill(int(fillColor.iRed * 255),
		 int(fillColor.iGreen * 255),
		 int(fillColor.iBlue * 255));
    QBrush brush;
    brush.setColor(iDimmed ? qfill.light() : qfill);
    brush.setStyle(QBrush::SolidPattern);
    /*
    if (qfill.red() > qfill.green())
      brush.setStyle(QBrush::Dense4Pattern);
    else
      brush.setStyle(QBrush::CrossPattern);
    */
    iPainter->setBrush(brush);
    iPainter->setPen(QPainter::NoPen);

    QPoint org;
    std::vector<QPoint> pv;
    pv.reserve(iV.size());

    uint k = 0;
    QPoint start = iV[0];

    for (uint i = 0; i < iType.size(); ++i) {
      switch (iType[i]) {
      case EMoveTo:
	org = iV[k];
	// fall through
      case ELineTo:
	pv.push_back(iV[k++]);
	break;
      case ECurveTo:
	{
	  QPointArray ctrlPts(4);
	  ctrlPts.setPoint(0, iV[k-1]);
	  ctrlPts.setPoint(1, iV[k++]);
	  ctrlPts.setPoint(2, iV[k++]);
	  ctrlPts.setPoint(3, iV[k++]);
#if QT_VERSION >= 0x030000
	  QPointArray arr = ctrlPts.cubicBezier();
#else
	  QPointArray arr = ctrlPts.quadBezier();
#endif
	  std::copy(arr.begin(), arr.end(), std::back_inserter(pv));
	}
	break;
      case EEndPath:
      case EEndClosedPath:
	pv.push_back(org);
	if (org != start)
	  pv.push_back(start);
	break; // nothing
      }
    }
    QPointArray arr;
    int n = pv.size();
    arr.setRawData(&pv[0], n);
    // Exploit that Index() of null attribute is zero.
    bool wind = (WindRule().Index() != 0);
    iPainter->drawPolygon(arr, wind);
    arr.resetRawData(&pv[0], n);
  }
  IpeAttribute stroke = Stroke();
  if (!stroke.IsNullOrVoid()) {
    // Now 'stroke' must be an absolute color
    assert(stroke.IsAbsolute());
    IpeColor strokeColor = rep->ToColor(stroke);
    iPainter->setBrush(QPainter::NoBrush);
    QPen pen;
    QColor qstroke(int(strokeColor.iRed * 255),
		   int(strokeColor.iGreen * 255),
		   int(strokeColor.iBlue * 255));
    pen.setColor(iDimmed ? qstroke.light() : qstroke);
    // width
    if (LineWidth()) {
      assert(LineWidth().IsAbsolute());
      double wid = iZoom * rep->ToScalar(LineWidth()).ToDouble();
      pen.setWidth(uint(wid + 0.5));
    }
    // Exploit that Index() of null attribute is zero.
    switch (LineJoin().Index()) {
    case IpeStrokeStyle::EMiterJoin:
      pen.setJoinStyle(Qt::MiterJoin);
      break;
    case IpeStrokeStyle::ERoundJoin:
      pen.setJoinStyle(Qt::RoundJoin);
      break;
    case IpeStrokeStyle::EBevelJoin:
      pen.setJoinStyle(Qt::BevelJoin);
      break;
    }
    switch (LineCap().Index()) {
    case IpeStrokeStyle::EButtCap:
      pen.setCapStyle(Qt::FlatCap);
      break;
    case IpeStrokeStyle::ERoundCap:
      pen.setCapStyle(Qt::RoundCap);
      break;
    case IpeStrokeStyle::ESquareCap:
      pen.setCapStyle(Qt::SquareCap);
      break;
    }
    IpeAttribute dash = DashStyle();
    if (!dash.IsNullOrSolid()) {
      // now comes the tricky part
      if (dash == iDash[0])
	pen.setStyle(Qt::DashLine);
      else if (dash == iDash[1])
	pen.setStyle(Qt::DotLine);
      else if (dash == iDash[2])
	pen.setStyle(Qt::DashDotLine);
      else if (dash == iDash[3])
	pen.setStyle(Qt::DashDotDotLine);
      else
	pen.setStyle(Qt::DotLine);
    }
    iPainter->setPen(pen);

    QPoint org;
    std::vector<QPoint> pv;
    pv.reserve(iV.size());

    uint k = 0;
    for (uint i = 0; i < iType.size(); ++i) {
      switch (iType[i]) {
      case EMoveTo:
	org = iV[k];
	// fall through
      case ELineTo:
	pv.push_back(iV[k++]);
	break;
      case ECurveTo:
	{
	  QPointArray ctrlPts(4);
	  ctrlPts.setPoint(0, iV[k-1]);
	  ctrlPts.setPoint(1, iV[k++]);
	  ctrlPts.setPoint(2, iV[k++]);
	  ctrlPts.setPoint(3, iV[k++]);
#if QT_VERSION >= 0x030000
	  QPointArray arr = ctrlPts.cubicBezier();
#else
	  QPointArray arr = ctrlPts.quadBezier();
#endif
	  std::copy(arr.begin(), arr.end(), std::back_inserter(pv));
	}
	break;
      case EEndClosedPath:
#ifdef WIN32
	pv.push_back(org);
#endif
      case EEndPath:
	{
	  QPointArray arr;
	  int n = pv.size();
	  arr.setRawData(&pv[0], n);
#ifndef WIN32
	  // drawPolygon appears to be buggy, at least on Qt 2.3.0 on Win2000
	  if (iType[i] == EEndClosedPath)
	    iPainter->drawPolygon(arr);
	  else
#endif
	    iPainter->drawPolyline(arr);
	  arr.resetRawData(&pv[0], n);
	  pv.clear();
	  break;
	}
      }
    }
  }
  iType.clear();
  iV.clear();
}

void IpeCanvasPainter::DoDrawBitmap(IpeBitmap bitmap)
{
  if (!bitmap.RenderData()) {
    // todo: should we remember if this failed?
    IpeBuffer data = bitmap.PixelData();
    if (data.size() > 0) {
      IpeRenderData *render = new IpeRenderData;
      // warning: data buffer must remain alive as long as img is
      QImage img((uchar *) data.data(), bitmap.Width(), bitmap.Height(), 32,
		 0, 0, QImage::IgnoreEndian);
      if (img.width() > iMaxBitmapSize || img.height() > iMaxBitmapSize) {
	int wid = img.width();
	int ht = img.height();
	double factor;
	if (wid > ht)
	  factor = wid / double(iMaxBitmapSize);
	else
	  factor = ht / double(iMaxBitmapSize);
	wid = int(wid / factor + 0.5);
	ht = int(ht / factor + 0.5);
	render->iImage = img.smoothScale(wid, ht);
      } else
	render->iImage = img.copy();
      // img no longer needed
      bitmap.SetRenderData(render);
    }
  }

  if (bitmap.RenderData()) {
    IpeRenderData *render = static_cast<IpeRenderData *>(bitmap.RenderData());
    IpeMatrix tf = Matrix() * IpeMatrix(1.0 / render->iImage.width(), 0.0,
					0.0, -1.0 / render->iImage.height(),
					0.0, 1.0);
    QWMatrix m;
    m.setMatrix(tf.iA[0], tf.iA[1], tf.iA[2], tf.iA[3], tf.iA[4], tf.iA[5]);
    iPainter->setWorldMatrix(m, true);
    iPainter->drawImage(0, 0, render->iImage);
    iPainter->resetXForm();
  }
}

void IpeCanvasPainter::DoDrawText(const IpeText *text)
{
  if (Stroke().IsNullOrVoid())
    return;
  // 'stroke' must be an absolute color
  assert(Stroke().IsAbsolute());

  // Current origin is lower left corner of text box

  // Draw bounding box rectangle
  if (!iPretty && !iDimmed) {
    QPen pen;
    pen.setColor(Qt::green);
    pen.setStyle(Qt::DotLine);
    iPainter->setPen(pen);
    iPainter->moveTo(QPt(Matrix() * IpeVector(0,0)));
    iPainter->lineTo(QPt(Matrix() * IpeVector(0, text->TotalHeight())));
    iPainter->lineTo(QPt(Matrix() * IpeVector(text->Width(),
					      text->TotalHeight())));
    iPainter->lineTo(QPt(Matrix() * IpeVector(text->Width(), 0)));
    iPainter->lineTo(QPt(Matrix() * IpeVector(0,0)));

    iPainter->setPen(QPainter::NoPen);
    iPainter->setBrush(Qt::green);
    QPoint ref = QPt(Matrix() * text->Align());
    iPainter->drawRect(ref.x() - 3, ref.y() - 3, 6, 6);
  }

  const IpeText::XForm *xf = text->GetXForm();

  if (!xf || !iFonts) {
    QString s = QIpe(text->Text());
    uint olen = s.length();
    int i = s.find('\n');
    if (i >= 0)
      s.truncate(i);
    s.truncate(30);
    if (s.length() < olen)
      s += "...";

    IpeColor stroke = StyleSheet()->Repository()->ToColor(Stroke());
    QColor col(int(stroke.iRed * 255), int(stroke.iGreen * 255),
	       int(stroke.iBlue * 255));
    iPainter->setPen(iDimmed ? col.light() : col);

    // QRect r = iPainter->fontMetrics().boundingRect(s);
    QPoint pt = QPt(Matrix().Translation());
    pt.setY(pt.y() - iPainter->fontMetrics().descent());
    //if (text->IsMiniPage())
    //pt.setY(pt.y() + 15.0);
    iPainter->drawText(pt, s);
  } else {
    Transform(IpeMatrix(xf->iStretch.iX, 0, 0, xf->iStretch.iY, 0, 0));
    Execute(xf->iStream);
  }
}

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

//! Clear PDF argument stack
void IpeCanvasPainter::ClearArgs()
{
  while (!iArgs.empty()) {
    delete iArgs.back();
    iArgs.pop_back();
  }
}

void IpeCanvasPainter::Execute(const IpeBuffer &buffer)
{
  IpeBufferSource source(buffer);
  IpePdfParser parser(source);
  iFont = 0;
  iFillRgb = 0x000000;
  iStrokeRgb = 0x000000;
  while (!parser.Eos()) {
    IpePdfToken tok = parser.Token();
    if (tok.iType != IpePdfToken::EOp) {
      const IpePdfObj *obj = parser.GetObject();
      if (!obj)
	break; // no further parsing attempted
      iArgs.push_back(obj);
    } else {
      // its an operator, execute it
      IpeString op = tok.iString;
      parser.GetToken();
      if (op == "cm")
	Opcm();
      else if (op == "Tf")
	OpTf();
      else if (op == "Td")
	OpTd();
      else if (op == "TJ")
	OpTJ();
      else if (op == "rg")
	Oprg(false);
      else if (op == "RG")
	Oprg(true);
      else if (op == "g")
	Opg(false);
      else if (op == "G")
	Opg(true);
      else if (op == "w")
	Opw();
      else if (op == "m")
	Opm();
      else if (op == "l")
	Opl();
      else if (op == "BT")
	OpBT();
      else if (op != "ET") {
	IpeString a;
	for (uint i = 0; i < iArgs.size(); ++i)
	  a += iArgs[i]->Repr() + " ";
	ipeDebug("Op %s (%s)", op.CString(), a.CString());
      }
      ClearArgs();
    }
  }
  ClearArgs();
}

void IpeCanvasPainter::Opg(bool stroke)
{
  if (iArgs.size() != 1 || !iArgs[0]->Number())
    return;
  int gr = int(iArgs[0]->Number()->Value() * 255);
  if (stroke)
    iStrokeRgb = qRgb(gr, gr, gr);
  else
    iFillRgb = qRgb(gr, gr, gr);
}


void IpeCanvasPainter::Oprg(bool stroke)
{
  if (iArgs.size() != 3 || !iArgs[0]->Number() || !iArgs[1]->Number()
      || !iArgs[2]->Number())
    return;
  int r = int(iArgs[0]->Number()->Value() * 255);
  int g = int(iArgs[1]->Number()->Value() * 255);
  int b = int(iArgs[2]->Number()->Value() * 255);
  QColor col(r, g, b);
  if (iDimmed)
    col = col.light();
  if (stroke)
    iStrokeRgb = col.rgb();
  else
    iFillRgb = col.rgb();
}

void IpeCanvasPainter::Opcm()
{
  if (iArgs.size() != 6)
    return;
  IpeMatrix m;
  for (int i = 0; i < 6; ++i) {
    if (!iArgs[i]->Number())
      return;
    m.iA[i] = iArgs[i]->Number()->Value();
  }
  Transform(m);
}

void IpeCanvasPainter::Opw()
{
  if (iArgs.size() != 1 || !iArgs[0]->Number())
    return;
  iLineWid = iArgs[0]->Number()->Value();

}

void IpeCanvasPainter::Opm()
{
  if (iArgs.size() != 2 || !iArgs[0]->Number() || !iArgs[1]->Number())
    return;
  IpeVector t(iArgs[0]->Number()->Value(), iArgs[1]->Number()->Value());
  iMoveTo = t;
}

void IpeCanvasPainter::Opl()
{
  if (iArgs.size() != 2 || !iArgs[0]->Number() || !iArgs[1]->Number())
    return;
  IpeVector t(iArgs[0]->Number()->Value(), iArgs[1]->Number()->Value());

  QPen pen;
  pen.setColor(QRgb(iStrokeRgb));
  pen.setWidth(uint(iZoom * iLineWid + 0.5));
  iPainter->setPen(pen);
  iPainter->moveTo(QPt(Matrix() * iMoveTo));
  iPainter->lineTo(QPt(Matrix() * t));
}

void IpeCanvasPainter::OpBT()
{
  iFont = 0;
  iTextPos = IpeVector::Zero;
}

void IpeCanvasPainter::OpTf()
{
  if (iArgs.size() != 2 || !iArgs[0]->Name() || !iArgs[1]->Number())
    return;
  IpeString name = iArgs[0]->Name()->Value();
  iFontSize = iArgs[1]->Number()->Value();
  if (name[0] != 'F')
    return;
  int font = IpeLex(name.substr(1)).GetInt();
  // ipeDebug("Setting font %d at %g", font, iFontSize);
  IpeLinear m = Matrix().Linear() * IpeLinear(iFontSize, 0, 0, iFontSize);
  iFont = iFonts->GetSize(font, m);
}

void IpeCanvasPainter::OpTd()
{
  if (iArgs.size() != 2 || !iArgs[0]->Number() || !iArgs[1]->Number())
    return;
  IpeVector t(iArgs[0]->Number()->Value(), iArgs[1]->Number()->Value());
  iTextPos = iTextPos + t;
}

void IpeCanvasPainter::OpTJ()
{
  if (!iFont || iArgs.size() != 1 || !iArgs[0]->Array())
    return;
  IpeVector pos = iTextPos;
  for (int i = 0; i < iArgs[0]->Array()->Count(); ++i) {
    const IpePdfObj *obj = iArgs[0]->Array()->Obj(i, 0);
    if (obj->Number()) {
      pos.iX -= 0.001 * iFontSize * obj->Number()->Value();
    } else if (obj->String()) {
      IpeString s = obj->String()->Value();
      for (int j = 0; j < s.size(); ++j) {
	uchar ch = s[j];
	QPoint pt = QPt(Matrix() * pos);
	DrawChar(ch, iFillRgb, pt.x(), pt.y());
	pos.iX += 0.001 * iFontSize * iFont->Face()->Width(ch);
      }
    }
  }
}

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

//! Draw a glyph.
/*! Glyph is drawn with hotspot at position (x,y) (on the pixmap) */
bool IpeCanvasPainter::DrawChar(int ch, QRgb rgb, int x, int y)
{
  if (!iFont)
    return false;

  int w = iPixmap->width();
  int h = iPixmap->height();

  // generate the glyph pixmap
  int xOffset, yOffset, gw, gh;
  uchar *p = iFont->GetGlyph(ch, xOffset, yOffset, gw, gh);
  if (!p)
    return false;

  // compute: (x0,y0) = position in destination pixmap
  //          (x1,y1) = position in glyph image
  //          (w0,h0) = size of image transfer
  int x0, y0, x1, y1, w0, h0;
  x0 = x - xOffset;
  y0 = y - yOffset;
  x1 = 0;
  y1 = 0;
  w0 = gw;
  h0 = gh;
  if (x0 < 0) {
    x1 = -x0;
    w0 += x0;
    x0 = 0;
  }
  if (x0 + w0 > w)
    w0 = w - x0;
  if (w0 < 0)
    return true;
  if (y0 < 0) {
    y1 = -y0;
    h0 += y0;
    y0 = 0;
  }
  if (y0 + h0 > h)
    h0 = h - y0;
  if (h0 < 0)
    return true;

  QPixmap dst(gw, gh);
  bitBlt(&dst, x1, y1, iPixmap, x0, y0, w0, h0);
  QImage image = dst.convertToImage();

  if (iFonts->AntiAlias()) {
    int r = qRed(rgb);
    int g = qGreen(rgb);
    int b = qBlue(rgb);
    // stuff the glyph pixmap into the image
    for (int yy = 0; yy < gh; ++yy) {
      uint *dst = (uint *) image.scanLine(yy);
      for (int xx = 0; xx < gw; ++xx) {
	int pix = *p++ & 0xff;
	int pix1 = 0xff - pix;
	if (pix > 0) {
	  uint bg = *dst;
	  int newr = (pix * r + pix1 * qRed(bg)) / 255;
	  int newg = (pix * g + pix1 * qGreen(bg)) / 255;
	  int newb = (pix * b + pix1 * qBlue(bg)) / 255;
	  *dst = qRgb(newr, newg, newb);
	}
	++dst;
      }
    }
  } else {
    // one color
    for (int yy = 0; yy < gh; ++yy) {
      uint *dst = (uint *) image.scanLine(yy);
      for (int xx = 0; xx < gw; xx += 8) {
	int pix = *p++;
	for (int xx1 = xx; xx1 < xx + 8 && xx1 < gw; ++xx1) {
	  if (pix & 0x80)
	    *dst = rgb;
	  pix <<= 1;
	  ++dst;
	}
      }
    }
  }
  dst.convertFromImage(image);
  bitBlt(iPixmap, x0, y0, &dst, x1, y1, w0, h0);
  return true;
}

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