
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2016 Matthias Koefferlein

  This program 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.

  This program 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 this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/


#include "edtPropertiesPageUtils.h"

#include "dbShapes.h"
#include "dbLayout.h"

namespace edt
{

// -------------------------------------------------------------------------
//  CombinedChangeApplicator implementation

CombinedChangeApplicator::CombinedChangeApplicator ()
{
}
    
CombinedChangeApplicator::CombinedChangeApplicator (ChangeApplicator *a1)
{
  m_appl.push_back (a1);
}
    
CombinedChangeApplicator::CombinedChangeApplicator (ChangeApplicator *a1, ChangeApplicator *a2)
{
  m_appl.push_back (a1);
  m_appl.push_back (a2);
}
    
void CombinedChangeApplicator::add (ChangeApplicator *a)
{
  m_appl.push_back (a);
}
    
CombinedChangeApplicator::~CombinedChangeApplicator ()
{
  for (std::vector<ChangeApplicator *>::const_iterator a = m_appl.begin (); a != m_appl.end (); ++a) {
    delete *a;
  }
  m_appl.clear ();
}

bool CombinedChangeApplicator::supports_relative_mode () const 
{ 
  for (std::vector<ChangeApplicator *>::const_iterator a = m_appl.begin (); a != m_appl.end (); ++a) {
    if ((*a)->supports_relative_mode ()) {
      return true;
    }
  }
  return false; 
}

db::Shape CombinedChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool relative) const 
{
  db::Shape s = shape;
  for (std::vector<ChangeApplicator *>::const_iterator a = m_appl.begin (); a != m_appl.end (); ++a) {
    if (*a) {
      s = (*a)->do_apply (shapes, s, dbu, relative);
    }
  }
  return s;
}

db::Instance CombinedChangeApplicator::do_apply_inst (db::Cell &cell, const db::Instance &instance, double dbu, bool relative) const 
{
  db::Instance i = instance;
  for (std::vector<ChangeApplicator *>::const_iterator a = m_appl.begin (); a != m_appl.end (); ++a) {
    if (*a) {
      i = (*a)->do_apply_inst (cell, i, dbu, relative);
    }
  }
  return i;
}

// -------------------------------------------------------------------------
//  ChangePropertiesApplicator implementation

ChangePropertiesApplicator::ChangePropertiesApplicator (db::properties_id_type prop_id) 
  : m_prop_id (prop_id)
{ 
  //  .. nothing yet ...
}

db::Shape ChangePropertiesApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double /*dbu*/, bool /*relative*/) const 
{
  return shapes.replace_prop_id (shape, m_prop_id);
}

db::Instance ChangePropertiesApplicator::do_apply_inst (db::Cell &cell, const db::Instance &instance, double /*dbu*/, bool /*relative*/) const 
{
  return cell.replace_prop_id (instance, m_prop_id);
}

// -------------------------------------------------------------------------
//  BoxDimensionsChangeApplicator implementation

BoxDimensionsChangeApplicator::BoxDimensionsChangeApplicator (db::Coord dl, db::Coord db, db::Coord dr, db::Coord dt, db::Coord l, db::Coord b, db::Coord r, db::Coord t)
  : m_dl (dl), m_db (db), m_dr (dr), m_dt (dt), m_l (l), m_b (b), m_r (r), m_t (t)
{
  //  .. nothing yet ..
}

db::Shape BoxDimensionsChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool relative) const
{
  db::Box org_box;
  shape.box (org_box);

  db::Box new_box;
  if (relative) {
    new_box = db::Box (org_box.left () + m_dl, 
                       org_box.bottom () + m_db,
                       org_box.right () + m_dr, 
                       org_box.top () + m_dt);
  } else {

    db::Coord l = org_box.left ();
    db::Coord r = org_box.right ();

    if (m_dl != 0 && m_dr == 0) {
      //  left side is fixed
      l = m_l;
    } else if (m_dl == 0 && m_dr != 0) {
      //  right side is fixed
      r = m_r;
    } else if (m_dl != 0 && m_dl == m_dr) {
      //  center is fixed
      l = (m_l + m_r) / 2 - org_box.width () / 2;
      r = (m_l + m_r) / 2 + org_box.width () / 2;
    } else if (m_dl != 0 && m_dl == -m_dr) {
      //  width is fixed
      l = org_box.center ().x () - (m_r - m_l) / 2;
      r = org_box.center ().x () + (m_r - m_l) / 2;
    } else if (m_dl != 0 && m_dr != 0) {
      //  both sides have changed
      l = m_l;
      r = m_r;
    }

    db::Coord b = org_box.bottom ();
    db::Coord t = org_box.top ();

    if (m_db != 0 && m_dt == 0) {
      //  left side is fixed
      b = m_b;
    } else if (m_db == 0 && m_dt != 0) {
      //  right side is fixed
      t = m_t;
    } else if (m_db != 0 && m_db == m_dt) {
      //  center is fixed
      b = (m_b + m_t) / 2 - org_box.height () / 2;
      t = (m_b + m_t) / 2 + org_box.height () / 2;
    } else if (m_db != 0 && m_db == -m_dt) {
      //  height is fixed
      b = org_box.center ().y () - (m_t - m_b) / 2;
      t = org_box.center ().y () + (m_t - m_b) / 2;
    } else if (m_db != 0 && m_dt != 0) {
      //  both sides have changed
      b = m_b;
      t = m_t;
    }

    new_box = db::Box (l, b, r, t);

  }

  if (new_box != org_box) {
    //  shape changed - replace the old by the new one
    return shapes.replace (shape, new_box);
  } else {
    //  shape did not change
    return shape;
  }
}

// -------------------------------------------------------------------------
//  PolygonChangeApplicator implementation

PolygonChangeApplicator::PolygonChangeApplicator (const db::Polygon &poly, const db::Polygon &org_poly)
  : m_poly (poly), m_org_poly (org_poly)
{
  //  .. nothing yet ..
}

db::Shape PolygonChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool relative) const
{
  db::Polygon org_poly;
  shape.polygon (org_poly);

  if (relative) {

    db::Polygon new_poly = m_poly.moved (org_poly.box ().p1 () - m_poly.box ().p1 ());

    if (new_poly != org_poly) {
      //  shape changed - replace the old by the new one
      return shapes.replace (shape, new_poly);
    } else {
      //  shape did not change
      return shape;
    }

  } else if (m_poly != org_poly) {
    //  shape changed - replace the old by the new one
    return shapes.replace (shape, m_poly);
  } else {
    //  shape did not change
    return shape;
  }
}

// -------------------------------------------------------------------------
//  TextOrientationChangeApplicator implementation

TextOrientationChangeApplicator::TextOrientationChangeApplicator (const db::FTrans &trans)
  : ChangeApplicator (), m_trans (trans)
{
  //  .. nothing yet ..
}

db::Shape TextOrientationChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool /*relative*/) const
{
  db::Text org_text;
  shape.text (org_text);

  db::Text new_text = org_text;
  new_text.trans (db::Trans (org_text.trans ().disp ()) * db::Trans (m_trans));

  if (new_text != org_text) {
    //  shape changed - replace the old by the new one
    return shapes.replace (shape, new_text);
  } else {
    //  shape did not change
    return shape;
  }
}

// -------------------------------------------------------------------------
//  TextPositionChangeApplicator implementation

TextPositionChangeApplicator::TextPositionChangeApplicator (const db::Point &disp, const db::Point &org_disp)
  : ChangeApplicator (), m_disp (disp), m_org_disp (org_disp)
{
  //  .. nothing yet ..
}

db::Shape TextPositionChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool relative) const
{
  db::Text org_text;
  shape.text (org_text);

  db::Text new_text = org_text;
  if (relative) {
    new_text.trans (db::Trans (m_disp - m_org_disp) * org_text.trans ());
  } else {
    db::Point np = org_text.trans ().disp ();
    if (m_disp.x () != m_org_disp.x ()) {
      np.set_x (m_disp.x ());
    }
    if (m_disp.y () != m_org_disp.y ()) {
      np.set_y (m_disp.y ());
    }
    new_text.trans (db::Trans (np - org_text.trans ().disp ()) * org_text.trans ());
  }

  if (new_text != org_text) {
    //  shape changed - replace the old by the new one
    return shapes.replace (shape, new_text);
  } else {
    //  shape did not change
    return shape;
  }
}

// -------------------------------------------------------------------------
//  TextHAlignChangeApplicator implementation

TextHAlignChangeApplicator::TextHAlignChangeApplicator (db::HAlign halign)
  : ChangeApplicator (), m_halign (halign)
{
  //  .. nothing yet ..
}

db::Shape TextHAlignChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool /*relative*/) const
{
  db::Text org_text;
  shape.text (org_text);

  db::Text new_text = org_text;
  new_text.halign (m_halign);

  if (new_text != org_text) {
    //  shape changed - replace the old by the new one
    return shapes.replace (shape, new_text);
  } else {
    //  shape did not change
    return shape;
  }
}

// -------------------------------------------------------------------------
//  TextVAlignChangeApplicator implementation

TextVAlignChangeApplicator::TextVAlignChangeApplicator (db::VAlign valign)
  : ChangeApplicator (), m_valign (valign)
{
  //  .. nothing yet ..
}

db::Shape TextVAlignChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool /*relative*/) const
{
  db::Text org_text;
  shape.text (org_text);

  db::Text new_text = org_text;
  new_text.valign (m_valign);

  if (new_text != org_text) {
    //  shape changed - replace the old by the new one
    return shapes.replace (shape, new_text);
  } else {
    //  shape did not change
    return shape;
  }
}

// -------------------------------------------------------------------------
//  TextSizeChangeApplicator implementation

TextSizeChangeApplicator::TextSizeChangeApplicator (db::Coord size)
  : ChangeApplicator (), m_size (size)
{
  //  .. nothing yet ..
}

db::Shape TextSizeChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool /*relative*/) const
{
  db::Text org_text;
  shape.text (org_text);

  db::Text new_text = org_text;
  new_text.size (m_size);

  if (new_text != org_text) {
    //  shape changed - replace the old by the new one
    return shapes.replace (shape, new_text);
  } else {
    //  shape did not change
    return shape;
  }
}

// -------------------------------------------------------------------------
//  TextStringChangeApplicatorimplementation

TextStringChangeApplicator::TextStringChangeApplicator (const std::string &string)
  : ChangeApplicator (), m_string (string)
{
  //  .. nothing yet ..
}

db::Shape TextStringChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool /*relative*/) const
{
  db::Text org_text;
  shape.text (org_text);

  db::Text new_text = org_text;
  new_text.string (m_string);

  if (new_text != org_text) {
    //  shape changed - replace the old by the new one
    return shapes.replace (shape, new_text);
  } else {
    //  shape did not change
    return shape;
  }
}

// -------------------------------------------------------------------------
//  PathPointsChangeApplicator implementation

PathPointsChangeApplicator::PathPointsChangeApplicator (const std::vector<db::Point> &points, const std::vector<db::Point> &org_points)
  : ChangeApplicator (), m_points (points), m_org_points (org_points)
{
  //  .. nothing yet ..
}

db::Shape PathPointsChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool relative) const
{
  db::Path org_path;
  shape.path (org_path);

  db::Path new_path = org_path;
  if (relative && ! m_org_points.empty () && new_path.begin () != new_path.end ()) {
    std::vector<db::Point> new_points = m_points;
    for (std::vector<db::Point>::iterator p = new_points.begin (); p != new_points.end (); ++p) {
      *p += *new_path.begin () - m_org_points.front ();
    }
    new_path.assign (new_points.begin (), new_points.end ());
  } else {
    new_path.assign (m_points.begin (), m_points.end ());
  }

  if (new_path != org_path) {
    //  shape changed - replace the old by the new one
    return shapes.replace (shape, new_path);
  } else {
    //  shape did not change
    return shape;
  }
}

// -------------------------------------------------------------------------
//  PathWidthChangeApplicator implementation

PathWidthChangeApplicator::PathWidthChangeApplicator (db::Coord w, db::Coord org_w)
  : ChangeApplicator (), m_width (w), m_org_width (org_w)
{
  //  .. nothing yet ..
}

db::Shape PathWidthChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool relative) const
{
  db::Path org_path;
  shape.path (org_path);

  db::Path new_path = org_path;
  if (relative) {
    new_path.width (new_path.width () + m_width - m_org_width);
  } else {
    new_path.width (m_width);
  }

  //  Adjust extensions if equal to half width if that was the case before
  if (org_path.bgn_ext () == org_path.width () / 2) {
    new_path.bgn_ext (new_path.width () / 2);
  }
  if (org_path.end_ext () == org_path.width () / 2) {
    new_path.end_ext (new_path.width () / 2);
  }

  if (new_path != org_path) {
    //  shape changed - replace the old by the new one
    return shapes.replace (shape, new_path);
  } else {
    //  shape did not change
    return shape;
  }
}

// -------------------------------------------------------------------------
//  PathStartExtensionChangeApplicator implementation

PathStartExtensionChangeApplicator::PathStartExtensionChangeApplicator (db::Coord e)
  : ChangeApplicator (), m_ext (e)
{
  //  .. nothing yet ..
}

db::Shape PathStartExtensionChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool /*relative*/) const
{
  db::Path org_path;
  shape.path (org_path);

  db::Path new_path = org_path;
  if (m_ext == std::numeric_limits <db::Coord>::min ()) {
    new_path.bgn_ext (new_path.width () / 2);
  } else {
    new_path.bgn_ext (m_ext);
  }

  if (new_path != org_path) {
    //  shape changed - replace the old by the new one
    return shapes.replace (shape, new_path);
  } else {
    //  shape did not change
    return shape;
  }
}

// -------------------------------------------------------------------------
//  PathEndExtensionChangeApplicator implementation

PathEndExtensionChangeApplicator::PathEndExtensionChangeApplicator (db::Coord e)
  : ChangeApplicator (), m_ext (e)
{
  //  .. nothing yet ..
}

db::Shape PathEndExtensionChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool /*relative*/) const
{
  db::Path org_path;
  shape.path (org_path);

  db::Path new_path = org_path;
  if (m_ext == std::numeric_limits <db::Coord>::min ()) {
    new_path.end_ext (new_path.width () / 2);
  } else {
    new_path.end_ext (m_ext);
  }

  if (new_path != org_path) {
    //  shape changed - replace the old by the new one
    return shapes.replace (shape, new_path);
  } else {
    //  shape did not change
    return shape;
  }
}

// -------------------------------------------------------------------------
//  PathRoundEndChangeApplicator implementation

PathRoundEndChangeApplicator::PathRoundEndChangeApplicator (bool r)
  : ChangeApplicator (), m_round (r)
{
  //  .. nothing yet ..
}

db::Shape PathRoundEndChangeApplicator::do_apply (db::Shapes &shapes, const db::Shape &shape, double dbu, bool /*relative*/) const
{
  db::Path org_path;
  shape.path (org_path);

  db::Path new_path = org_path;
  new_path.round (m_round);

  if (new_path != org_path) {
    //  shape changed - replace the old by the new one
    return shapes.replace (shape, new_path);
  } else {
    //  shape did not change
    return shape;
  }
}

// -------------------------------------------------------------------------
//  ChangeTargetCellApplicator implementation

ChangeTargetCellApplicator::ChangeTargetCellApplicator (db::cell_index_type cell_index)
  : m_cell_index (cell_index)
{
  //  .. nothing yet ..
}

db::Instance ChangeTargetCellApplicator::do_apply_inst (db::Cell &cell, const db::Instance &instance, double dbu, bool /*relative*/) const 
{
  tl_assert (cell.layout ());

  //  detect recursions in the hierarchy
  std::set<db::cell_index_type> called;
  cell.layout ()->cell (m_cell_index).collect_called_cells (called);
  if (m_cell_index == cell.cell_index () || called.find (cell.cell_index ()) != called.end ()) {
    throw tl::Exception (tl::to_string (QObject::tr ("Trying to build a recursive hierarchy")).c_str ());
  }

  db::CellInstArray arr = instance.cell_inst ();
  arr.object ().cell_index (m_cell_index);
  return cell.replace (instance, arr);
}

// -------------------------------------------------------------------------
//  ChangeInstanceTransApplicator implementation

ChangeInstanceTransApplicator::ChangeInstanceTransApplicator (double a, double org_a, bool mirror, bool org_mirror, double m, double org_m, const db::DPoint &disp, const db::DPoint &org_disp)
  : m_angle (a), m_org_angle (org_a), m_mirror (mirror), m_org_mirror (org_mirror),
    m_mag (m), m_org_mag (org_m), m_disp (disp), m_org_disp (org_disp)
{
  //  .. nothing yet ..
}

db::Instance ChangeInstanceTransApplicator::do_apply_inst (db::Cell &cell, const db::Instance &instance, double dbu, bool relative) const 
{
  db::CellInstArray::complex_trans_type tr = instance.complex_trans ();

  if (relative) {

    tr = db::CellInstArray::complex_trans_type (m_mag, m_angle, m_mirror, m_disp * (1.0 / dbu)) * db::CellInstArray::complex_trans_type (m_org_mag, m_org_angle, m_org_mirror, m_org_disp * (1.0 / dbu)).inverted () * tr;

  } else {

    db::DPoint disp = tr.disp ();

    if (fabs (m_disp.x () - m_org_disp.x ()) > 1e-6) {
      disp.set_x (m_disp.x () / dbu);
    }
    if (fabs (m_disp.y () - m_org_disp.y ()) > 1e-6) {
      disp.set_y (m_disp.y () / dbu);
    }

    double mag = fabs (m_mag - m_org_mag) > 1e-6 ? m_mag : tr.mag ();
    double angle = fabs (m_angle - m_org_angle) > 1e-6 ? m_angle : tr.angle ();
    bool mirror = (m_mirror != m_org_mirror) ? m_mirror : tr.is_mirror ();

    tr = db::CellInstArray::complex_trans_type (mag, angle, mirror, disp);

  }

  bool is_complex = (tr.is_mag () || ! tr.is_ortho ());

  db::CellInstArray new_inst;

  db::CellInstArray::point_type a, b;
  unsigned long na = 0, nb = 0;

  if (instance.is_regular_array (a, b, na, nb)) {

    if (is_complex) {
      new_inst = db::CellInstArray (db::CellInst (instance.cell_index ()), tr, a, b, na, nb);
    } else {
      new_inst = db::CellInstArray (db::CellInst (instance.cell_index ()), db::Trans (tr.rot (), tr.disp ()), a, b, na, nb);
    }

  } else {

    if (is_complex) {
      new_inst = db::CellInstArray (db::CellInst (instance.cell_index ()), tr);
    } else {
      new_inst = db::CellInstArray (db::CellInst (instance.cell_index ()), db::Trans (tr.rot (), tr.disp ()));
    }

  }

  if (new_inst != instance.cell_inst ()) {
    return cell.replace (instance, new_inst);
  } else {
    return instance;
  }
}

// -------------------------------------------------------------------------
//  ChangeInstanceArrayApplicator implementation

ChangeInstanceArrayApplicator::ChangeInstanceArrayApplicator (const db::DPoint &a, bool set_a, const db::DPoint &b, bool set_b, unsigned long na, bool set_na, unsigned long nb, bool set_nb)
  : m_a (a), m_set_a (set_a), m_b (b), m_set_b (set_b),
    m_na (na), m_set_na (set_na), m_nb (nb), m_set_nb (set_nb)
{
  //  .. nothing yet ..
}

db::Instance ChangeInstanceArrayApplicator::do_apply_inst (db::Cell &cell, const db::Instance &instance, double dbu, bool /*relative*/) const 
{
  db::CellInstArray new_inst;

  db::CellInstArray::point_type a, b;
  unsigned long na = 0, nb = 0;
  if (! instance.is_regular_array (a, b, na, nb)) {
    na = nb = 1;
    a = db::Point::from_double (m_a * (1.0 / dbu));
    b = db::Point::from_double (m_b * (1.0 / dbu));
  }

  if (m_set_a) {
    a = db::Point::from_double (m_a * (1.0 / dbu));
  }
  if (m_set_na) {
    na = m_na;
  }
  if (m_set_b) {
    b = db::Point::from_double (m_b * (1.0 / dbu));
  }
  if (m_set_nb) {
    nb = m_nb;
  }

  if (instance.is_complex ()) {
    new_inst = db::CellInstArray (db::CellInst (instance.cell_index ()), instance.complex_trans (), a, b, na, nb);
  } else {
    new_inst = db::CellInstArray (db::CellInst (instance.cell_index ()), instance.front (), a, b, na, nb);
  }

  if (new_inst != instance.cell_inst ()) {
    return cell.replace (instance, new_inst);
  } else {
    return instance;
  }
}

// -------------------------------------------------------------------------
//  InstanceRemoveArrayApplicator implementation

InstanceRemoveArrayApplicator::InstanceRemoveArrayApplicator ()
{
  //  .. nothing yet ..
}

db::Instance InstanceRemoveArrayApplicator::do_apply_inst (db::Cell &cell, const db::Instance &instance, double dbu, bool /*relative*/) const 
{
  db::CellInstArray new_inst;
  if (instance.is_complex ()) {
    new_inst = db::CellInstArray (db::CellInst (instance.cell_index ()), instance.complex_trans ());
  } else {
    new_inst = db::CellInstArray (db::CellInst (instance.cell_index ()), instance.front ());
  }

  if (new_inst != instance.cell_inst ()) {
    return cell.replace (instance, new_inst);
  } else {
    return instance;
  }
}

// -------------------------------------------------------------------------
//  helper functions to convert coordinates

std::string
coord_to_string (double dc, double dbu, bool du)
{
  if (du) {
    return tl::db_to_string (dc);
  } else {
    return tl::micron_to_string (dc * dbu);
  }
}

db::DCoord
dcoord_from_dcoord (double d, double dbu, bool du, const db::CplxTrans &t)
{
  db::DCoord dc = t.ctrans (d * (du ? 1.0 : 1.0 / dbu));
  return db::Coord (floor (dc + 0.5)) * dbu;
}

db::Coord
coord_from_dcoord (double d, double dbu, bool du, const db::CplxTrans &t)
{
  db::DCoord dc = t.ctrans (d * (du ? 1.0 : 1.0 / dbu));
  return db::Coord (floor (dc + 0.5));
}

db::DPoint
dpoint_from_dpoint (const db::DPoint &dp, double dbu, bool du, const db::CplxTrans &t)
{
  return (t * (dp * (du ? 1.0 : 1.0 / dbu))) * dbu;
}

db::Point
point_from_dpoint (const db::DPoint &dp, double dbu, bool du, const db::CplxTrans &t)
{
  return (t * (dp * (du ? 1.0 : 1.0 / dbu)));
}

db::DCoord
dcoord_from_string (const char *txt, double dbu, bool du, const db::CplxTrans &t)
{
  double d = 0.0;
  tl::from_string (txt, d);
  return dcoord_from_dcoord (d, dbu, du, t);
}

db::Coord
coord_from_string (const char *txt, double dbu, bool du, const db::CplxTrans &t)
{
  double d = 0.0;
  tl::from_string (txt, d);
  return coord_from_dcoord (d, dbu, du, t);
}

std::string
coords_to_string (const db::DPoint &dp, double dbu, bool du, const char *sep)
{
  return coord_to_string (dp.x (), dbu, du) + sep + coord_to_string (dp.y (), dbu, du);
}

}

