// --------------------------------------------------------------------
// Ipe style sheet
// --------------------------------------------------------------------
/*

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

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

/*! \class IpeStyleSheet
  \ingroup doc
  \brief A style sheet maps symbolic names to absolute values.

  Ipe documents can use symbolic attributes, such as 'normal', 'fat',
  or 'thin' for line thickness, or 'red', 'navy', 'turquoise' for
  color.  The mapping to an absolute pen thickness or RGB value is
  performed by an IpeStyleSheet.

  IpeStyleSheet's cascade in the sense that a document can refer to
  several style sheets, which are arranged on a stack.  The document
  owns the 'top-level stylesheet', which in turn owns the stack below
  it.  A lookup is done from top to bottom, and returns as soon as a
  match is found. Ipe always appends the built-in "standard" style
  sheet at the bottom of the cascade.

  Style sheets are always included when the document is saved, so that
  an Ipe document is self-contained.

  The built-in style sheet is guaranteed not to change in ways that
  would change the meaning of existing Ipe files.  In other words, new
  symbolic names may be defined, but no existing ones will be removed
  or mapped differently.

  Note that the style sheet depends (and includes a pointer to) the
  IpeRepository of the document.  You cannot attach a style sheet to
  another document---the IpeRepository wouldn't make sense.  To
  transfer a style sheet, it has to be externalized and internalized
  again with the new repository.  For this reason, copying and
  assigning IpeStyleSheet is illegal.
*/

#define MASK 0x1fffffff

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

//! The default constructor creates an empty style sheet.
IpeStyleSheet::IpeStyleSheet(IpeRepository *rep)
{
  iStandard = false;
  iCascade = 0;
  iRepository = rep;
  iTLMargin.iX = -1.0; // no margins set
  iShading.iType = IpeShading::ENone;
}

//! Destructor.
IpeStyleSheet::~IpeStyleSheet()
{
  delete iCascade;
  for (TemplateMap::iterator it = iTemplates.begin();
       it != iTemplates.end(); ++it)
    delete it->second;
}

//! Copy constructor is disabled: it panics.
IpeStyleSheet::IpeStyleSheet(const IpeStyleSheet &)
{
  assert(false);
}

//! Assignment operator is disabled: it panics.
IpeStyleSheet &IpeStyleSheet::operator=(const IpeStyleSheet &)
{
  assert(false);
  return *this;
}

//! Return total LaTeX preamble (of the whole cascade).
IpeString IpeStyleSheet::TotalPreamble() const
{
  if (iCascade)
    return iCascade->TotalPreamble() + "\n" + iPreamble;
  else
    return iPreamble;
}

//! Set text margins.
void IpeStyleSheet::SetMargins(const IpeVector &tl, const IpeVector &br)
{
  iTLMargin = tl;
  iBRMargin = br;
}

//! Find text margins in style sheet cascade.
void IpeStyleSheet::FindMargins(IpeVector &tl, IpeVector &br) const
{
  if (iTLMargin.iX >= 0) {
    tl = iTLMargin;
    br = iBRMargin;
  } else if (iCascade) {
    iCascade->FindMargins(tl, br);
  } else {
    tl = IpeVector(0.0, 0.0);
    br = IpeVector(0.0, 0.0);
  }
}

//! Set the shading in this style sheet.
void IpeStyleSheet::SetShading(const IpeShading &s)
{
  iShading = s;
}

//! Find shading in style sheet cascade.
IpeShading IpeStyleSheet::FindShading() const
{
  if (iShading.iType != IpeShading::ENone || !iCascade)
    return iShading;
  else
    return iCascade->FindShading();
}


//! Add font \a s to list of fonts with cmap generation.
void IpeStyleSheet::AddCMap(IpeString s)
{
  iCMaps.push_back(s);
}

void IpeStyleSheet::AllCMaps(std::vector<IpeString> &seq) const
{
  if (iCascade)
    iCascade->AllCMaps(seq);
  std::copy(iCMaps.begin(), iCMaps.end(), std::back_inserter(seq));
}

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

//! Add a template object.
void IpeStyleSheet::AddTemplate(IpeAttribute name, const IpeObject *obj)
{
  if (!name.IsSymbolic())
    return;
  iTemplates[name.Index()] = obj;
}

//! Find a template object with given name.
const IpeObject *IpeStyleSheet::FindTemplate(IpeAttribute attr) const
{
  TemplateMap::const_iterator it = iTemplates.find(attr.Index());
  if (it != iTemplates.end())
    return it->second;
  else if (iCascade)
    return iCascade->FindTemplate(attr);
  else
    return 0;
}

//! Add an attribute.
void IpeStyleSheet::Add(IpeAttribute name, IpeAttribute value)
{
  if (!name.IsSymbolic())
    return;
  iMap[name.iName & MASK] = value;
}

//! Find a symbolic attribute.
/*! Returns \c null attribute if \a sym is \c null or cannot be found
  in the style sheet. In all other cases, returns an absolute
  attribute (one for which IsAbsolute() returns \c true). */
IpeAttribute IpeStyleSheet::Find(IpeAttribute sym) const
{
  if (sym.IsNull() || sym.IsAbsolute())
    return sym;
  Map::const_iterator it = iMap.find(sym.iName & MASK);
  if (it != iMap.end()) {
    return it->second;
  } else if (iCascade) {
    return iCascade->Find(sym);
  } else
    return IpeAttribute(); // null
}

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

//! Set the next level of the style sheet cascade. Takes ownership.
/*! The previous contents is \e not destroyed.
  You have to take care of that. */
void IpeStyleSheet::SetCascade(IpeStyleSheet *sheet)
{
  iCascade = sheet;
}

//! Return all symbolic names in the style sheet cascade.
/*! Names are simply appended from top to bottom of the cascade.
  Names are inserted only once. */
void IpeStyleSheet::AllNames(IpeKind kind, IpeAttributeSeq &seq) const
{
  if (kind == IpeAttribute::ETemplate) {
    for (TemplateMap::const_iterator it = iTemplates.begin();
	 it != iTemplates.end(); ++it) {
      IpeAttribute attr(kind, true, it->first);
      if (std::find(seq.begin(), seq.end(), attr) == seq.end())
	seq.push_back(attr);
    }
  } else {
    uint kindMatch = (kind << IpeAttribute::EKindShift);
    for (Map::const_iterator it = iMap.begin();
	 it != iMap.end(); ++it) {
      if (uint(it->first & IpeAttribute::EKindMask) == kindMatch) {
	IpeAttribute attr(it->first | IpeAttribute::ESymbolic);
	if (std::find(seq.begin(), seq.end(), attr) == seq.end())
	  seq.push_back(attr);
      }
    }
  }
  if (iCascade)
    iCascade->AllNames(kind, seq);
}

void IpeStyleSheet::AllNames(IpeAttributeSeq *attr) const
{
  for (int kind = 0; kind <= IpeAttribute::ETemplate; ++kind) {
    attr[kind].clear();
    if (kind == IpeAttribute::EColor) {
      attr[kind].push_back(IpeAttribute::Void());
      attr[kind].push_back(IpeAttribute::Black());
      attr[kind].push_back(IpeAttribute::White());
    } else if (kind == IpeAttribute::EDashStyle)
      attr[kind].push_back(IpeAttribute::Solid());
    AllNames(IpeKind(kind), attr[kind]);
  }
}

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

static const char *scalarNames[IpeAttribute::ETemplate + 1] = {
  "linewidth", "marksize",
  "arrowsize", "grid", "angle", "dashstyle",
  "textsize", "textstretch",
  "color", "media", "template", };

//! Save style sheet in XML format.
void IpeStyleSheet::SaveAsXml(IpeStream &stream) const
{
  stream << "<ipestyle";
  if (!iName.empty())
    stream << " name=\"" << iName << "\"";
  stream << ">\n";
  IpePainter painter(this);
  for (TemplateMap::const_iterator it = iTemplates.begin();
       it != iTemplates.end(); ++it) {
    stream << "<template name=\""
	   << iRepository->String
      (IpeAttribute(IpeAttribute::ETemplate, true, it->first))
	   << "\">\n";
    it->second->SaveAsXml(painter, stream, IpeString());
    stream << "</template>\n";
  }
  IpeRepository *rep = iRepository;
  for (Map::const_iterator it = iMap.begin(); it != iMap.end(); ++it) {
    int kind = (it->first >> IpeAttribute::EKindShift);
    stream << "<" << scalarNames[kind]
	   << " name=\""
	   << rep->String(IpeAttribute(it->first | IpeAttribute::ESymbolic))
	   << "\" value=\"" << rep->String(it->second) << "\"/>\n";
  }
  if (!iPreamble.empty()) {
    stream << "<preamble>";
    stream.PutXmlString(iPreamble);
    stream << "</preamble>\n";
  }
  if (iTLMargin.iX >= 0)
    stream << "<margins tl=\"" << iTLMargin
	   << "\" br=\"" << iBRMargin << "\"/>\n";
  if (iShading.iType != IpeShading::ENone) {
    stream << "<shading";
    switch (iShading.iType) {
    case IpeShading::EAxial:
      stream << " type=\"axial\" coords=\""
	     << iShading.iV[0] << " " << iShading.iV[1] << "\"";
      break;
    case IpeShading::ERadial:
      stream << " type=\"radial\" coords=\""
	     << iShading.iV[0] << " " << iShading.iRadius[0]
	     << iShading.iV[1] << " " << iShading.iRadius[1] << "\"";
    default:
      break;
    }
    stream << " colora=\"" << iShading.iColor[0] << "\"";
    stream << " colorb=\"" << iShading.iColor[1] << "\"";
    stream << " extend=\"" << int(iShading.iExtend[0])
	   << " " << int(iShading.iExtend[1]) << "\"/>\n";
  }
  stream << "</ipestyle>\n";
}

//! Save whole style sheet cascade in XML format.
void IpeStyleSheet::SaveCascadeAsXml(IpeStream &stream) const
{
  if (iCascade)
    iCascade->SaveCascadeAsXml(stream);
  if (!IsStandard())
    SaveAsXml(stream);
}

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