// -*- C++ -*-
// --------------------------------------------------------------------
// Ipe drawing interface
// --------------------------------------------------------------------
/*

    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 "ipepainter.h"

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

/*! \class IpePainter
 * \ingroup base
 * \brief Interface for drawing.

 IpePainter-derived classes are used for drawing to the screen and for
 generating PDF and Postscript output.

 The IpePainter maintains a stack of graphics states, which includes
 stroke and fill color, line width, dash style, miter limit, line cap
 and line join, and the current transformation matrix.  The IpePainter
 class takes care of maintaining this stack, and setting of the
 attributes in the current graphics state.

 Setting an attribute with a symbolic value is resolved immediately
 using the IpeStyleSheet attached to the IpePainter, so calling the
 Stroke() or Fill() methods of IpePainter will return the current
 absolute color.

 It's okay to set symbolic attributes that the stylesheet does not
 define - they are set to a default absolute value (normal, black,
 solid, etc.).

 A null color is drawn as if it was void.  Ipe objects exploit this:
 only group objects ever need to explicitly contain a 'void' color.

 A null dash style is drawn as solid.

 A null line width is drawn as whatever is the standard of the drawing
 medium.

 The painter is either in "general" or in "path construction" mode.
 The NewPath() member starts path construction mode. In this mode,
 only the path construction operators (MoveTo, LineTo, CurveTo, Rect,
 DrawArc, DrawEllipse, ClosePath), the transformation operators
 (Transform, Untransform, Translate), and the stack operators (Push,
 Pop) are admissible.  (Pushs and pops must balance inside path
 construction.  Note that pushs and pops inside path construction only
 affect the current (painter-internal) tranformation matrix, they do
 not generate Postscript gsave/grestore operators.) The path is drawn
 using DrawPath, this ends path construction mode.  Path construction
 operators cannot be used in general mode.

 Derived classes need to implement the DoXXX functions for drawing paths,
 images, and texts.
*/

//! Constructor takes a (cascaded) style sheet, which is not owned.
/*! The initial graphics state contains null attributes and default
  line cap and line join. */
IpePainter::IpePainter(const IpeStyleSheet *style)
{
  iStyleSheet = style;
  State state;
  iState.push_back(state);
  iInPath = 0;
}

//! Virtual destructor.
IpePainter::~IpePainter()
{
  // nothing
}

//! Concatenate a matrix to current transformation matrix.
void IpePainter::Transform(const IpeMatrix &m)
{
  iState.back().iMatrix = iState.back().iMatrix * m;
}

//! Reset transformation to original one, but with different origin/direction.
/*! This changes the current transformation matrix to the one set
  before the first Push operation, but maintaining the current origin.
  If \a direct is \c true, then the x-direction is also kept.
*/
void IpePainter::Untransform(bool direct)
{
  IpeVector org = Matrix().Translation();
  IpeVector dir = Matrix().Linear() * IpeVector(1,0);
  iState.back().iMatrix = IpeMatrix(iState.front().iMatrix.Linear());
  if (direct) {
    // compute what direction is transformed to dir by original matrix
    IpeAngle alpha = (iState.back().iMatrix.Inverse() * dir).Angle();
    // ensure that (1,0) is rotated into this orientation
    iState.back().iMatrix = iState.back().iMatrix * IpeLinear(alpha);
  }
  iState.back().iMatrix.iA[4] = org.iX;
  iState.back().iMatrix.iA[5] = org.iY;
}

//! Concatenate a translation to current transformation matrix.
void IpePainter::Translate(const IpeVector &v)
{
  IpeMatrix m;
  m.iA[4] = v.iX;
  m.iA[5] = v.iY;
  iState.back().iMatrix = iState.back().iMatrix * m;
}

//! Enter path construction mode.
void IpePainter::NewPath()
{
  assert(!iInPath);
  iInPath = iState.size(); // save current nesting level
  DoNewPath();
}

//! Start a new subpath.
void IpePainter::MoveTo(const IpeVector &v)
{
  assert(iInPath > 0);
  DoMoveTo(v);
}

//! Add line segment to current subpath.
void IpePainter::LineTo(const IpeVector &v)
{
  assert(iInPath > 0);
  DoLineTo(v);
}

//! Add a Bezier segment to current subpath.
void IpePainter::CurveTo(const IpeVector &v1, const IpeVector &v2,
			 const IpeVector &v3)
{
  assert(iInPath > 0);
  DoCurveTo(v1, v2, v3);
}

//! Add a rectangle subpath to the path.
/*! Has a default implementation in terms of MoveTo and LineTo, but
  derived classes can reimplement for efficiency. */
void IpePainter::Rect(const IpeRect &re)
{
  MoveTo(re.Min());
  LineTo(re.BottomRight());
  LineTo(re.Max());
  LineTo(re.TopLeft());
  ClosePath();
}

//! Close the current subpath.
void IpePainter::ClosePath()
{
  assert(iInPath > 0);
  DoClosePath();
}

//! Save current graphics state.
void IpePainter::Push()
{
  State state = iState.back();
  iState.push_back(state);
  if (!iInPath)
    DoPush();
}

//! Restore previous graphics state.
void IpePainter::Pop()
{
  assert(int(iState.size()) > IpeMax(1, iInPath));
  iState.pop_back();
  if (!iInPath)
    DoPop();
}

//! Fill and/or stroke a path (depending on color).
/*! As in PDF, an "path" can consist of several subpaths. */
void IpePainter::DrawPath()
{
  // check that pushs and pops next inside path construction
  assert(iInPath == int(iState.size()));
  DoDrawPath();
  iInPath = 0;
}

//! Render a bitmap.
/*! Assumes the transformation matrix has been set up to map the unit
  square to the image area on the paper.
*/
void IpePainter::DrawBitmap(IpeBitmap bitmap)
{
  assert(!iInPath);
  DoDrawBitmap(bitmap);
}

//! Render a text object.
/*! Stroke color is already set, and the origin is the lower-left
  corner of the text box. */
void IpePainter::DrawText(const IpeText *text)
{
  assert(!iInPath);
  DoDrawText(text);
}

//! Set stroke color, resolving symbolic color.
void IpePainter::SetStroke(IpeAttribute color)
{
  iState.back().iStroke = iStyleSheet->Find(color);
}

//! Set fill color, resolving symbolic color.
void IpePainter::SetFill(IpeAttribute color)
{
  iState.back().iFill = iStyleSheet->Find(color);
}

//! Set line width, resolving symbolic value.
void IpePainter::SetLineWidth(IpeAttribute wid)
{
  iState.back().iLineWidth = iStyleSheet->Find(wid);
}

//! Set dash style, resolving symbolic value.
void IpePainter::SetDashStyle(IpeAttribute dash)
{
  iState.back().iDashStyle = iStyleSheet->Find(dash);
}

//! Set line cap.
void IpePainter::SetLineCap(IpeAttribute cap)
{
  iState.back().iLineCap = cap;
}

//! Set line join.
void IpePainter::SetLineJoin(IpeAttribute join)
{
  iState.back().iLineJoin = join;
}

//! Set wind rule (wind or even-odd).
void IpePainter::SetWindRule(IpeAttribute rule)
{
  iState.back().iWindRule = rule;
}

//! Set font size of text objects.
/*! Paradoxically, this isn't actually used to render text, but for
  saving Ipegroup objects!  Text goes through the IpeLatex interface,
  and the visitor that scans for text objects and writes them to the
  Latex source file finds the text size information itself.
*/

void IpePainter::SetTextSize(IpeAttribute size)
{
  iState.back().iTextSize = iStyleSheet->Find(size);
}

//! Set size of mark objects.
void IpePainter::SetMarkSize(IpeAttribute size)
{
  iState.back().iMarkSize = iStyleSheet->Find(size);
}

//! Set shape of mark objects.
void IpePainter::SetMarkShape(int shape)
{
  iState.back().iMarkShape = shape;
}

// Coordinate for bezier approximation for quarter circle.
const double BETA = 0.55228474983079334;
const double PI15 = IpePi + IpeHalfPi;

//! Draw the unit circle.
/*! PDF does not have an "arc" or "circle" primitive, so to draw an
  arc, circle, or ellipse, Ipe has to translate it into a sequence of
  Bezier curves.

  The approximation is based on the following: The unit circle arc
  from (1,0) to (cos a, sin a) be approximated by a Bezier spline with
  control points (1, 0), (1, beta) and their mirror images along the
  line with slope a/2, where
  beta = 4.0 * (1.0 - cos(a/2)) / (3 * sin(a/2))

  Ipe draws circles by drawing four Bezier curves for the quadrants,
  and arcs by patching together quarter circle approximations with a
  piece computed from the formula above.

  This does not modify the transformation matrix.  The path is
  generated as a sequence MoveTo .. CurveTo .. ClosePath operators,
  but is not actually drawn (DrawPath is not called).
*/
void IpePainter::DrawEllipse()
{
  IpeVector p0(1.0, 0.0);
  IpeVector p1(1.0, BETA);
  IpeVector p2(BETA, 1.0);
  IpeVector p3(0.0, 1.0);
  IpeVector q1(-BETA, 1.0);
  IpeVector q2(-1.0, BETA);
  IpeVector q3(-1.0, 0.0);

  MoveTo(p0);
  CurveTo(p1, p2, p3);
  CurveTo(q1, q2, q3);
  CurveTo(-p1, -p2, -p3);
  CurveTo(-q1, -q2, -q3);
  ClosePath();
}

//! Draw an arc of the unit circle of length \a alpha.
/*! \a alpha is normalized to [0, 2 pi], and applied starting from the
  point (1,0).

  The function works by generating a sequence of Bezier splines (see
  DrawEllipse for details of the transformation).  It only generates
  calls to CurveTo.  It is assumed that the caller has already
  executed a MoveTo to the beginning of the arc at (1,0).

  This function may modify the transformation matrix.
*/
void IpePainter::DrawArc(double alpha)
{
  // IpeVector p0(1.0, 0.0);
  IpeVector p1(1.0, BETA);
  IpeVector p2(BETA, 1.0);
  IpeVector p3(0.0, 1.0);
  IpeVector q1(-BETA, 1.0);
  IpeVector q2(-1.0, BETA);
  IpeVector q3(-1.0, 0.0);

  alpha = IpeAngle(alpha).Normalize(0.0);

  double begAngle = 0.0;
  if (alpha > IpeHalfPi) {
    CurveTo(p1, p2, p3);
    begAngle = IpeHalfPi;
  }
  if (alpha > IpePi) {
    CurveTo(q1, q2, q3);
    begAngle = IpePi;
  }
  if (alpha > PI15) {
    CurveTo(-p1, -p2, -p3);
    begAngle = PI15;
  }
  alpha -= begAngle;
  double alpha2 = alpha / 2.0;
  double divi = 3.0 * sin(alpha2);
  if (divi == 0.0)
    return;  // alpha2 is close to zero
  double beta = 4.0 * (1.0 - cos(alpha2)) / divi;
  IpeLinear m = IpeLinear(IpeAngle(begAngle));

  IpeVector pp1(1.0, beta);
  IpeVector pp2 = IpeLinear(IpeAngle(alpha)) * IpeVector(1.0, -beta);
  IpeVector pp3 = IpeVector(IpeAngle(alpha));

  CurveTo(m * pp1, m * pp2, m * pp3);
}

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

//! Perform push on output medium (if necessary).
/*! This is not called when client uses Push during path construction! */
void IpePainter::DoPush()
{
  // nothing
}

//! Perform pop on output medium (if necessary).
/*! This is not called when client uses Pop during path construction! */
void IpePainter::DoPop()
{
  // nothing
}

//! Perform new path operator.
void IpePainter::DoNewPath()
{
  // nothing
}

//! Perform moveto operator.
void IpePainter::DoMoveTo(const IpeVector &)
{
  // nothing
}

//! Perform lineto operator.
void IpePainter::DoLineTo(const IpeVector &)
{
  // nothing
}

//! Perform curveto operator.
void IpePainter::DoCurveTo(const IpeVector &, const IpeVector &,
			   const IpeVector &)
{
  // nothing
}

//! Perform closepath operator.
void IpePainter::DoClosePath()
{
  // nothing
}

//! Actually draw the path.
void IpePainter::DoDrawPath()
{
  // nothing
}

//! Draw a bitmap.
void IpePainter::DoDrawBitmap(IpeBitmap)
{
  // nothing
}

//! Draw a text object.
void IpePainter::DoDrawText(const IpeText *)
{
  // nothing
}

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