// --------------------------------------------------------------------
// The Text object.
// --------------------------------------------------------------------
/*

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

/*! \class IpeText
  \ingroup obj
  \brief The text object.

  The text object is a tricky Ipe object.  It has a Latex-source
  representation, which needs to be translated into PDF by Pdflatex
  before it can be saved as PDF.

  There are two main types of text objects, labels and minipages.  The
  IsMiniPage() method tells you which.  The title and textbox types
  are variations on these that indicate the function of the text
  object in a document---that is useful to reposition text objects
  after the style sheet has been changed.  Use Type() to determine the
  precise type of a text object.

  The dimensions of a text object are given by Width(), Height(), and
  Depth().  They are recomputed by Ipe when running LaTeX, with the
  exception of the width for minipage objects (whose width is fixed).
  Before Latex has been run, the dimensions are not reliable.

  The position of the reference point relative to the text box is
  given by VerticalAlignment() and HorizontalAlignment().

  Text can be either transformable, or fixed.  In the first case, the
  Matrix() is applied to the complete text box, in the second case,
  the Matrix() is only applied to the reference point Position(), and
  the text box remains fixed relative to the reference point.

  The Text() must be a legal LaTeX fragment that can be interpreted by
  LaTeX inside \c \\hbox, possibly using the macros or packages
  defined in the preamble.
*/

//! Create from XML stream.
IpeText::IpeText(IpeRepository *rep, const IpeXmlAttributes &attr,
		 IpeString data)
  : IpeObject(rep, attr)
{
  iXForm = 0;
  iText = data;
  // parse position
  IpeLex st(attr["pos"]);
  st >> iPos.iX >> iPos.iY;

  iTransformable = false;
  IpeString str;
  if (attr.Has("size", str))
    iSize = rep->MakeScalar(IpeAttribute::ETextSize, str);
  if (attr.Has("transformable", str) && str == "yes")
    iTransformable = true;

  iType = ELabel;
  bool hasType = attr.Has("type", str);
  if (hasType) {
    if (str == "minipage")
      iType = EMinipage;
    else if (str == "title")
      iType = ETitle;
    else if (str == "textbox")
      iType = ETextbox;
  }

  iWidth = 10.0;
  if (attr.Has("width", str)) {
    iWidth = IpeLex(str).GetDouble();
    if (!hasType)
      iType = EMinipage;
  }

#if 1
  // obsolete - remove later
  if (attr.Has("textstretch", str)) {
    iWidth *= IpeLex(str).GetDouble();
  }
#endif

  iHeight = 10.0;
  if (attr.Has("height", str))
    iHeight = IpeLex(str).GetDouble();

  iDepth = 0.0;
  if (attr.Has("depth", str))
    iDepth = IpeLex(str).GetDouble();

  iVerticalAlignment = IsMiniPage() ? ETop : EBottom;
  if (attr.Has("valign", str)) {
    if (str == "top")
      iVerticalAlignment = ETop;
    else if (str == "bottom")
      iVerticalAlignment = EBottom;
    else if (str == "baseline")
      iVerticalAlignment = EBaseline;
    else if (str == "center")
      iVerticalAlignment = EVCenter;
  }
  iHorizontalAlignment = ELeft;
  if (attr.Has("halign", str)) {
    if (str == "left")
      iHorizontalAlignment = ELeft;
    else if (str == "right")
      iHorizontalAlignment = ERight;
    else if (str == "center")
      iHorizontalAlignment = EHCenter;
  }
}

//! Create text object.
IpeText::IpeText(const IpeAllAttributes &attr, IpeString data,
		 const IpeVector &pos, TType type, IpeScalar width)
  : IpeObject(attr)
{
  iXForm = 0;
  iText = data;
  iSize = attr.iTextSize;
  iPos = pos;
  iType = type;
  iWidth = width;
  iHeight = 10.0;
  iDepth = 0.0;
  if (iType == ELabel || iType == ETitle)
    iVerticalAlignment = EBottom;
  else
    iVerticalAlignment = ETop;
  iHorizontalAlignment = ELeft;
  iTransformable = attr.iTransformable;
}

//! Clone object
IpeObject *IpeText::Clone() const
{
  return new IpeText(*this);
}

//! Copy constructor.
IpeText::IpeText(const IpeText &rhs)
  : IpeObject(rhs)
{
  iPos = rhs.iPos;
  iText = rhs.iText;
  iSize = rhs.iSize;
  iWidth = rhs.iWidth;
  iHeight = rhs.iHeight;
  iDepth = rhs.iDepth;
  iType = rhs.iType;
  iVerticalAlignment = rhs.iVerticalAlignment;
  iHorizontalAlignment = rhs.iHorizontalAlignment;
  iXForm = rhs.iXForm;
  iTransformable = rhs.iTransformable;
  if (iXForm) iXForm->iRefCount++;
}

//! Destructor.
IpeText::~IpeText()
{
  if (iXForm && --iXForm->iRefCount == 0)
    delete iXForm;
}


//! Return pointer to this object.
IpeText *IpeText::AsText()
{
  return this;
}

//! Call VisitText of visitor.
void IpeText::Accept(IpeVisitor &visitor) const
{
  visitor.VisitText(this);
}

//! Save object to XML stream.
void IpeText::SaveAsXml(IpePainter &painter, IpeStream &stream,
			IpeString layer) const
{
  stream << "<text";
  SaveAttributesAsXml(painter, stream, layer);
  stream << " pos=\"" << iPos.iX << " " << iPos.iY << "\"";
  switch (iType) {
  case ELabel:
    stream << " type=\"label\"";
    break;
  case EMinipage:
    stream << " type=\"minipage\"";
    break;
  case ETextbox:
    stream << " type=\"textbox\"";
    break;
  case ETitle:
    stream << " type=\"title\"";
    break;
  }
  if (iXForm || IsMiniPage())
    stream << " width=\"" << iWidth << "\"";
  if (iXForm)
    stream << " height=\"" << iHeight << "\""
	   << " depth=\"" << iDepth << "\"";
  switch (iHorizontalAlignment) {
  case ELeft:
    break;
  case EHCenter:
    stream << " halign=\"center\"";
    break;
  case ERight:
    stream << " halign=\"right\"";
    break;
  }
  switch (iVerticalAlignment) {
  case ETop:
    stream << " valign=\"top\"";
    break;
  case EBottom:
    stream << " valign=\"bottom\"";
    break;
  case EBaseline:
    stream << " valign=\"baseline\"";
    break;
  case EVCenter:
    stream << " valign=\"center\"";
    break;
  }
  if (iTransformable)
    stream << " transformable=\"yes\"";
  if (!painter.TextSize())
    stream << " size=\"" << painter.Repository()->String(iSize) << "\">";
  stream.PutXmlString(iText);
  stream << "</text>\n";
}

//! Save text as PDF.
void IpeText::Draw(IpePainter &painter) const
{
  IpeVector pos(iPos);
  painter.Push();
  painter.Transform(Matrix());
  painter.Translate(pos);
  if (!iTransformable)
    painter.Untransform(false);
  painter.SetStroke(Stroke());
  painter.SetTextSize(Size()); // this size isn't actually used
  // Adjust alignment: make lower left corner of text box the origin
  painter.Translate(-Align());
  painter.DrawText(this);
  painter.Pop();
}

double IpeText::Distance(const IpeVector &v, const IpeMatrix &m,
			 double bound) const
{
  IpeVector u[5];
  Quadrilateral(m, u);
  u[4] = u[0];

  double d = bound;
  double d1;
  for (int i = 0; i < 4; ++i) {
    if ((d1 = IpeSegment(u[i], u[i+1]).Distance(v, d)) < d)
      d = d1;
  }
  return d1;
}

void IpeText::AddToBBox(IpeRect &box, const IpeMatrix &m) const
{
  IpeVector v[4];
  Quadrilateral(m, v);

  for (int i = 0; i < 4; ++i)
    box.AddPoint(v[i]);
}

// void IpeText::SnapVtx(const IpeVector &mouse, const IpeMatrix &m,
// 		      IpeVector &pos, double &bound) const
void IpeText::SnapVtx(const IpeVector &, const IpeMatrix &,
		      IpeVector &, double &) const
{
  // nothing
}

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

//! Set width of paragraph.
/*! This invalidates (and destroys) the XForm.
  The function panics if object is not a minipage. */
void IpeText::SetWidth(IpeScalar width)
{
  assert(IsMiniPage());
  iWidth = width;
  SetXForm(0);
}

//! Set font size of text.
/*! This invalidates (and destroys) the XForm. */
void IpeText::SetSize(IpeAttribute size)
{
  iSize = size;
  SetXForm(0);
}

//! Set whether the text object can be transformed.
/*! This invalidates (and destroys) the XForm. */
void IpeText::SetTransformable(bool transf)
{
  iTransformable = transf;
  SetXForm(0);
}

//! Sets the text of the text object.
/*! This invalidates (and destroys) the XForm. */
void IpeText::SetText(IpeString text)
{
  iText = text;
  SetXForm(0);
}

//! Change type.
void IpeText::SetType(TType type)
{
  iType = type;
}

//! Change horizontal alignment (text moves with respect to reference point).
void IpeText::SetHorizontalAlignment(THorizontalAlignment align)
{
  iHorizontalAlignment = align;
}

//! Change vertical alignment (text moves with respect to reference point).
void IpeText::SetVerticalAlignment(TVerticalAlignment align)
{
  iVerticalAlignment = align;
}

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

//! Check symbolic size attribute.
void IpeText::CheckStyle(const IpeStyleSheet *sheet,
			  IpeAttributeSeq &seq) const
{
  CheckSymbol(iSize, sheet, seq);
  IpeObject::CheckStyle(sheet, seq);
}

//! Return quadrilateral including the text.
/*! This is the bounding box, correctly transformed by Matrix(),
  taking into consideration whether the object is transformable.
 */
void IpeText::Quadrilateral(const IpeMatrix &m, IpeVector v[4]) const
{
  IpeScalar wid = iWidth;
  IpeScalar ht = TotalHeight();
  IpeVector offset = -Align();
  v[0] = offset + IpeVector(0, 0);
  v[1] = offset + IpeVector(wid, 0);
  v[2] = offset + IpeVector(wid, ht);
  v[3] = offset + IpeVector(0, ht);

  IpeMatrix m1 = m * Matrix();
  if (iTransformable) {
    IpeLinear m2 = m1.Linear();
    for (int i = 0; i < 4; ++i)
      v[i] = m2 * v[i];
  }
  IpeVector pos = m1 * iPos;
  for (int i = 0; i < 4; ++i)
    v[i] = v[i] + pos;
}

//! Update the PDF code for this object.
void IpeText::SetXForm(XForm *xform) const
{
  if (iXForm && --iXForm->iRefCount == 0)
    delete iXForm;
  iXForm = xform;
  if (iXForm) {
    iXForm->iRefCount = 1;
    iDepth = iXForm->iStretch.iY * iXForm->iDepth / 100.0;
    iHeight = iXForm->iStretch.iY * iXForm->iBBox.Height() - iDepth;
    if (!IsMiniPage())
      iWidth = iXForm->iStretch.iX * iXForm->iBBox.Width();
  }
}

//! Return position of reference point in text box coordinate system.
/*! Assume a coordinate system where the text box has corners (0,0)
  and (Width(), TotalHeight()).  This function returns the coordinates
  of the reference point in this coordinate system. */
IpeVector IpeText::Align() const
{
  IpeVector align(0.0, 0.0);
  switch (iVerticalAlignment) {
  case ETop:
    align.iY = TotalHeight();
    break;
  case EBottom:
    break;
  case EVCenter:
    align.iY = 0.5 * TotalHeight();
    break;
  case EBaseline:
    align.iY = Depth();
    break;
  }
  switch (iHorizontalAlignment) {
  case ELeft:
    break;
  case ERight:
    align.iX = Width();
    break;
  case EHCenter:
    align.iX = 0.5 * Width();
    break;
  }
  return align;
}

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