
/*

  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 "extRS274XApertures.h"
#include "extRS274XReader.h"
#include "dbPolygonTools.h"

namespace ext
{

// -----------------------------------------------------------------------------
//  RS274ApertureBase implementation

RS274XApertureBase::RS274XApertureBase ()
  : mp_ep (0), mp_reader (0), m_needs_update (true)
{ 
  // .. nothing yet ..
}

void 
RS274XApertureBase::produce_flash (const db::DPoint &d, RS274XReader &reader, db::EdgeProcessor &ep, bool clear)
{
  if (m_needs_update) {

    mp_reader = &reader;
    mp_ep = &ep;

    m_lines.clear ();
    m_polygons.clear ();
    m_clear_polygons.clear ();

    do_produce_flash ();

    if (! m_clear_polygons.empty ()) {
      std::vector<db::Polygon> input;
      m_polygons.swap (input);
      ep.boolean (input, m_clear_polygons, m_polygons, db::BooleanOp::ANotB, false, true);
      m_clear_polygons.clear ();
    }

    m_needs_update = false;

    mp_reader = 0;
    mp_ep = 0;

  }

  db::CplxTrans trans = db::CplxTrans (reader.dbu ());

  for (std::vector <db::Polygon>::const_iterator p = m_polygons.begin (); p != m_polygons.end (); ++p) {
    reader.produce_polygon (p->transformed (trans).moved (d), clear);
  }
  for (std::vector <db::Path>::const_iterator p = m_lines.begin (); p != m_lines.end (); ++p) {
    reader.produce_line (p->transformed (trans).moved (d), clear);
  }
}

void 
RS274XApertureBase::produce_linear (const db::DPoint &from, const db::DPoint &to, RS274XReader &reader, db::EdgeProcessor &ep, bool clear) 
{
  mp_reader = &reader;
  mp_ep = &ep;

  std::vector<db::Polygon> p, cp;
  std::vector<db::Path> l;

  l.swap (m_lines);
  p.swap (m_polygons);
  cp.swap (m_clear_polygons);

  if (! do_produce_linear (from, to)) {

    // fallback: produce flash and employ a Minkowsky sum to generate the resulting structure
    do_produce_flash ();

    double dbu = mp_reader->dbu ();
    db::Point ifrom (db::coord_traits<db::Coord>::rounded (from.x () / dbu), db::coord_traits<db::Coord>::rounded (from.y () / dbu));
    db::Point ito (db::coord_traits<db::Coord>::rounded (to.x () / dbu), db::coord_traits<db::Coord>::rounded (to.y () / dbu));

    std::vector<db::Polygon> p;

    if (! m_clear_polygons.empty ()) {
      ep.boolean (m_polygons, m_clear_polygons, p, db::BooleanOp::ANotB, false, true);
      m_clear_polygons.clear ();
      m_polygons.clear ();
    } else {
      m_polygons.swap (p);
    }

    for (std::vector<db::Polygon>::const_iterator f = p.begin (); f != p.end (); ++f) {
      m_polygons.push_back (db::minkowsky_sum (*f, db::Edge (ifrom, ito), true /*resolve holes*/));
    }
    
  }

  if (! m_clear_polygons.empty ()) {
    std::vector<db::Polygon> input;
    m_polygons.swap (input);
    ep.boolean (input, m_clear_polygons, m_polygons, db::BooleanOp::ANotB, false, true);
    m_clear_polygons.clear ();
  }

  db::CplxTrans trans = db::CplxTrans (mp_reader->dbu ());

  for (std::vector <db::Polygon>::const_iterator p = m_polygons.begin (); p != m_polygons.end (); ++p) {
    mp_reader->produce_polygon (p->transformed (trans), clear);
  }
  for (std::vector <db::Path>::const_iterator p = m_lines.begin (); p != m_lines.end (); ++p) {
    mp_reader->produce_line (p->transformed (trans), clear);
  }

  mp_reader = 0;
  mp_ep = 0;

  l.swap (m_lines);
  p.swap (m_polygons);
  cp.swap (m_clear_polygons);
}

void 
RS274XApertureBase::clear_points ()
{
  m_points.clear ();
}

void 
RS274XApertureBase::add_point (double x, double y)
{
  double dbu = mp_reader->dbu ();
  m_points.push_back (db::Point (db::coord_traits<db::Coord>::rounded (x / dbu), db::coord_traits<db::Coord>::rounded (y / dbu)));
}

void 
RS274XApertureBase::add_point (const db::DPoint &d)
{
  double dbu = mp_reader->dbu ();
  m_points.push_back (db::Point (db::coord_traits<db::Coord>::rounded (d.x () / dbu), db::coord_traits<db::Coord>::rounded (d.y () / dbu)));
}

void 
RS274XApertureBase::produce_circle (double cx, double cy, double r, bool clear)
{
  clear_points ();

  int n_circle = mp_reader->get_circle_points ();

  //  adjust the radius so we get a outer approximation of the circle:
  //  r *= 1.0 / cos (M_PI / double (n_circle));

  for (int i = 0; i < n_circle; ++i) {
    double a = M_PI * 2.0 * ((double (i) + 0.5) / double (n_circle));
    add_point (cx + r * cos (a), cy + r * sin (a));
  }

  produce_polygon (clear);
}

void 
RS274XApertureBase::produce_line ()
{
  m_lines.push_back (db::Path (m_points.begin (), m_points.end (), 0));
}

void 
RS274XApertureBase::produce_polygon (bool clear)
{
  if (clear) {

    m_clear_polygons.push_back (db::Polygon ());
    m_clear_polygons.back ().assign_hull (m_points.begin (), m_points.end ());

  } else {

    if (! m_clear_polygons.empty ()) {
      std::vector<db::Polygon> input;
      m_polygons.swap (input);
      mp_ep->boolean (input, m_clear_polygons, m_polygons, db::BooleanOp::ANotB, false, true);
      m_clear_polygons.clear ();
    }

    m_polygons.push_back (db::Polygon ());
    m_polygons.back ().assign_hull (m_points.begin (), m_points.end ());

  }
}

// -----------------------------------------------------------------------------
//  RS274XCircleAperture implementation

RS274XCircleAperture::RS274XCircleAperture (RS274XReader &reader, tl::Extractor &ex)
  : m_d (0.0), m_dx (0.0), m_dy (0.0)
{
  ex.expect(",");
  ex.read (m_d);
  ex.test(",");
  if (ex.test ("X")) {
    ex.read (m_dx);
  }
  ex.test(",");
  if (ex.test ("X")) {
    ex.read (m_dy);
  }
  ex.expect_end ();

  m_d = reader.um (m_d);
  m_dx = reader.um (m_dx);
  m_dy = reader.um (m_dy);
}

void 
RS274XCircleAperture::do_produce_flash () 
{
  // produce outer circle
  produce_circle (0.0, 0.0, m_d * 0.5, false);
  
  if (m_dx > 0.0 && m_dy == 0.0) {

    // produce circle hole
    produce_circle (0.0, 0.0, m_dx * 0.5, true);

  } else if (m_dx > 0.0 && m_dy > 0.0) {

    // produce square hole
    clear_points ();
    add_point (db::DPoint (-m_dx * 0.5, -m_dy * 0.5));
    add_point (db::DPoint (m_dx * 0.5, -m_dy * 0.5));
    add_point (db::DPoint (m_dx * 0.5, m_dy * 0.5));
    add_point (db::DPoint (-m_dx * 0.5, m_dy * 0.5));
    produce_polygon (true);

  }
}

bool 
RS274XCircleAperture::do_produce_linear (const db::DPoint &from, const db::DPoint &to) 
{
  if (m_dx > 0.0 || m_dy > 0.0) {
    return false;
  }

  if (m_d < 1e-10) {

    //  zero radius: draw a line rather than a aperture 
    clear_points ();
    add_point (from);
    add_point (to);
    produce_line ();

  } else {

    db::DPoint p (to - from);
    if (p.sq_distance () < 1e-10) {

      produce_circle (from.x (), from.y (), m_d * 0.5, false);

    } else {

      clear_points ();

      p.transform (db::DFTrans (db::DFTrans::r270));

      int n_circle = reader ().get_circle_points ();

      //  adjust the radius so we get an outer approximation of the circle
      double r = 0.5 * m_d / cos (M_PI / double (n_circle));
      p *= r / p.distance ();

      double a = -2.0 * M_PI / double (n_circle);

      p = db::DPoint (p.x() * cos(a * 0.5) - p.y() * sin(a * 0.5), p.x() * sin(a * 0.5) + p.y() * cos(a * 0.5));

      for (int i = 0; i < n_circle / 2; ++i) {
        add_point (from + p);
        p = db::DPoint (p.x() * cos(a) - p.y() * sin(a), p.x() * sin(a) + p.y() * cos(a));
      }

      for (int i = 0; i < n_circle / 2; ++i) {
        add_point (to + p);
        p = db::DPoint (p.x() * cos(a) - p.y() * sin(a), p.x() * sin(a) + p.y() * cos(a));
      }

      produce_polygon (false);

    }

  }

  return true;
}

// -----------------------------------------------------------------------------
//  RS274XRectAperture implementation

RS274XRectAperture::RS274XRectAperture (ext::RS274XReader &reader, tl::Extractor &ex)
  : m_dx (0.0), m_dy (0.0), m_hx (0.0), m_hy (0.0)
{
  ex.expect(",");
  ex.read (m_dx);
  ex.test(",");
  if (ex.test ("X")) {
    ex.read (m_dy);
  }
  ex.test(",");
  if (ex.test ("X")) {
    ex.read (m_hx);
  }
  ex.test(",");
  if (ex.test ("X")) {
    ex.read (m_hy);
  }
  ex.expect_end ();

  m_dx = reader.um (m_dx);
  m_dy = reader.um (m_dy);
  m_hx = reader.um (m_hx);
  m_hy = reader.um (m_hy);
}

void 
RS274XRectAperture::do_produce_flash ()
{
  // produce outer box
  clear_points ();
  add_point (db::DPoint (-m_dx * 0.5, -m_dy * 0.5));
  add_point (db::DPoint (m_dx * 0.5, -m_dy * 0.5));
  add_point (db::DPoint (m_dx * 0.5, m_dy * 0.5));
  add_point (db::DPoint (-m_dx * 0.5, m_dy * 0.5));
  produce_polygon (false);
  
  if (m_hx > 0.0 && m_hy > 0.0) {

    // produce square hole
    clear_points ();
    add_point (db::DPoint (-m_hx * 0.5, -m_hy * 0.5));
    add_point (db::DPoint (m_hx * 0.5, -m_hy * 0.5));
    add_point (db::DPoint (m_hx * 0.5, m_hy * 0.5));
    add_point (db::DPoint (-m_hx * 0.5, m_hy * 0.5));
    produce_polygon (true);

  }
}

bool  
RS274XRectAperture::do_produce_linear (const db::DPoint &from, const db::DPoint &to) 
{
  return false;
}

// -----------------------------------------------------------------------------
//  RS274XOvalAperture implementation

RS274XOvalAperture::RS274XOvalAperture (ext::RS274XReader &reader, tl::Extractor &ex)
  : m_dx (0.0), m_dy (0.0), m_hx (0.0), m_hy (0.0)
{
  ex.expect(",");
  ex.read (m_dx);
  ex.test(",");
  if (ex.test ("X")) {
    ex.read (m_dy);
  }
  ex.test(",");
  if (ex.test ("X")) {
    ex.read (m_hx);
  }
  ex.test(",");
  if (ex.test ("X")) {
    ex.read (m_hy);
  }
  ex.expect_end ();

  m_dx = reader.um (m_dx);
  m_dy = reader.um (m_dy);
  m_hx = reader.um (m_hx);
  m_hy = reader.um (m_hy);
}

void 
RS274XOvalAperture::do_produce_flash ()
{
  int n_circle = reader ().get_circle_points ();

  // produce outer oval
  clear_points ();

  if (m_dx - m_dy > 1e-4) {

    double r = m_dy / cos (M_PI / double (n_circle));

    for (int i = 0; i < n_circle / 2; ++i) {
      double a = -M_PI * 0.5 - M_PI * 2.0 * ((double (i) + 0.5) / double (n_circle));
      add_point (0.5 * (-m_dx + m_dy + r * cos (a)), 0.5 * r * sin (a));
    }

    for (int i = 0; i < n_circle / 2; ++i) {
      double a = M_PI * 0.5 - M_PI * 2.0 * ((double (i) + 0.5) / double (n_circle));
      add_point (0.5 * (m_dx - m_dy + r * cos (a)), 0.5 * r * sin (a));
    }

  } else if (m_dy - m_dx > 1e-4) {

    double r = m_dx / cos (M_PI / double (n_circle));

    for (int i = 0; i < n_circle / 2; ++i) {
      double a = -M_PI * 2.0 * ((double (i) + 0.5) / double (n_circle));
      add_point (0.5 * r * cos (a), 0.5 * (-m_dy + m_dx + r * sin (a)));
    }

    for (int i = 0; i < n_circle / 2; ++i) {
      double a = M_PI - M_PI * 2.0 * ((double (i) + 0.5) / double (n_circle));
      add_point (0.5 * r * cos (a), 0.5 * (m_dy - m_dx + r * sin (a)));
    }

  } else {

    //  intentionally create a polygon confined within (!) the circle (in the other cases, 
    //  this must not be the case to maintain the width, here this is not necessary)
    for (int i = 0; i < n_circle; ++i) {
      double a = -M_PI * 2.0 * ((double (i) + 0.5) / double (n_circle));
      add_point (0.5 * m_dx * cos (a), 0.5 * m_dx * sin (a));
    }

  }

  produce_polygon (false);
  
  if (m_hx > 0.0 && m_hy == 0.0) {

    // produce circle hole
    produce_circle (0.0, 0.0, m_hx * 0.5, true);

  } else if (m_hx > 0.0 && m_hy > 0.0) {

    // produce square hole
    clear_points ();
    add_point (db::DPoint (-m_hx * 0.5, -m_hy * 0.5));
    add_point (db::DPoint (m_hx * 0.5, -m_hy * 0.5));
    add_point (db::DPoint (m_hx * 0.5, m_hy * 0.5));
    add_point (db::DPoint (-m_hx * 0.5, m_hy * 0.5));
    produce_polygon (true);

  }
}

bool 
RS274XOvalAperture::do_produce_linear (const db::DPoint &from, const db::DPoint &to) 
{
  return false;
}

// -----------------------------------------------------------------------------
//  RS274XRegularAperture implementation

RS274XRegularAperture::RS274XRegularAperture (ext::RS274XReader &reader, tl::Extractor &ex)
  : m_d (0.0), m_a (0.0), m_nsides (0), m_hx (0.0), m_hy (0.0)
{
  ex.expect(",");
  ex.read (m_d);
  ex.test(",");
  ex.expect ("X");
  ex.read (m_nsides);

  ex.test(",");
  if (ex.test ("X")) {
    ex.read (m_a);
  }
  ex.test(",");
  if (ex.test ("X")) {
    ex.read (m_hx);
  }
  ex.test(",");
  if (ex.test ("X")) {
    ex.read (m_hy);
  }

  ex.expect_end ();

  m_d = reader.um (m_d);
  m_hx = reader.um (m_hx);
  m_hy = reader.um (m_hy);
}

void 
RS274XRegularAperture::do_produce_flash ()
{
  // produce outer regular polygon
  clear_points ();

  for (int i = 0; i < m_nsides; ++i) {
    double a = M_PI * 2.0 * double (i) / double (m_nsides) + m_a * (M_PI / 180.0);
    add_point (0.5 * m_d * cos (a), 0.5 * m_d * sin (a));
  }

  produce_polygon (false);
  
  if (m_hx > 0.0 && m_hy > 0.0) {

    // produce square hole
    clear_points ();
    add_point (db::DPoint (-m_hx * 0.5, -m_hy * 0.5));
    add_point (db::DPoint (m_hx * 0.5, -m_hy * 0.5));
    add_point (db::DPoint (m_hx * 0.5, m_hy * 0.5));
    add_point (db::DPoint (-m_hx * 0.5, m_hy * 0.5));
    produce_polygon (true);

  }
}

bool 
RS274XRegularAperture::do_produce_linear (const db::DPoint &from, const db::DPoint &to) 
{
  return false;
}

// -----------------------------------------------------------------------------
//  RS274XMacroAperture implementation

RS274XMacroAperture::RS274XMacroAperture (ext::RS274XReader &reader, const std::string &name, const std::string &def, tl::Extractor &ex)
  : m_name (name), m_def (def), m_unit (1.0)
{ 
  //  use the unit at definition time, not at execution time
  m_unit = reader.unit ();

  while (! ex.at_end ()) {
    double p;
    if (! ex.test (",") && ! ex.test ("X")) {
      ex.expect_end ();
    }
    ex.read (p);
    m_parameters.push_back (p);
  }
}

void 
RS274XMacroAperture::do_produce_flash ()
{
  try {
    do_produce_flash_internal ();
  } catch (tl::Exception &ex) {
    throw tl::Exception (ex.msg () + " (" + tl::to_string (QObject::tr ("expanding macro")) + " " + m_name + ")");
  }
}

void 
RS274XMacroAperture::do_produce_flash_internal ()
{
  tl::Extractor ex (m_def.c_str ());
  bool clear = false;

  while (! ex.at_end ()) {

    if (ex.test ("$")) {

      int nvar = 0;
      ex.read (nvar);
      nvar -= 1;

      ex.expect ("=");

      double value = read_expr (ex);

      if (nvar >= 0) {
        while (int (m_parameters.size ()) <= nvar) {
          m_parameters.push_back (0.0);
        }
        m_parameters[nvar] = value;
      }

    } else if (! ex.test ("*")) {

      int code = 0;
      ex.read (code);

      if (code == 1) {

        int pol = 0;
        ex.expect (",");
        ex.read (pol);

        if (pol == 0) {
          clear = true;
        } else if (pol == 1) {
          clear = false;
        } else if (pol == 2) {
          clear = !clear;
        } else {
          throw tl::Exception (tl::to_string (QObject::tr ("Invalid exposure code '%d'")), pol);
        }

        ex.expect (",");
        double d = read_expr (ex, true);
        ex.expect (",");
        double cx = read_expr (ex, true);
        ex.expect (",");
        double cy = read_expr (ex, true);

        produce_circle (cx, cy, d * 0.5, clear);

      } else if (code == 2 || code == 20) {

        int pol = 0;
        ex.expect (",");
        ex.read (pol);

        if (pol == 0) {
          clear = true;
        } else if (pol == 1) {
          clear = false;
        } else if (pol == 2) {
          clear = !clear;
        } else {
          throw tl::Exception (tl::to_string (QObject::tr ("Invalid exposure code '%d'")), pol);
        }

        ex.expect (",");
        double w = read_expr (ex, true);
        ex.expect (",");
        double x1 = read_expr (ex, true);
        ex.expect (",");
        double y1 = read_expr (ex, true);
        ex.expect (",");
        double x2 = read_expr (ex, true);
        ex.expect (",");
        double y2 = read_expr (ex, true);
        ex.expect (",");
        double a = read_expr (ex);

        db::DCplxTrans t = db::DCplxTrans (1.0, a, false, db::DPoint ());
        db::DPoint from = db::DPoint (x1, y1);
        db::DPoint to   = db::DPoint (x2, y2);

        db::DPoint p (to - from);
        if (p.sq_distance () < 1e-10) {
          throw tl::Exception (tl::to_string (QObject::tr ("Identical start and end point in type 2 or 20 aperture macro primitive")));
        }

        clear_points ();

        p = db::DPoint (p.y(), -p.x()) * (0.5 * w / p.distance ());

        for (int i = 0; i < 2; ++i) {
          add_point (t * (from + p));
          p = db::DPoint (-p.x(), -p.y ());
        }

        for (int i = 0; i < 2; ++i) {
          p = db::DPoint (-p.x(), -p.y ());
          add_point (t * (to + p));
        }

        produce_polygon (clear);

      } else if (code == 21 || code == 22) {

        int pol = 0;
        ex.expect (",");
        ex.read (pol);

        if (pol == 0) {
          clear = true;
        } else if (pol == 1) {
          clear = false;
        } else if (pol == 2) {
          clear = !clear;
        } else {
          throw tl::Exception (tl::to_string (QObject::tr ("Invalid exposure code '%d'")), pol);
        }

        ex.expect (",");
        double w = read_expr (ex, true);
        ex.expect (",");
        double h = read_expr (ex, true);
        ex.expect (",");
        double x = read_expr (ex, true);
        ex.expect (",");
        double y = read_expr (ex, true);
        ex.expect (",");
        double a = read_expr (ex);

        if (code == 22) {
          //  TODO: clarify: how is rotation defined?
          x += 0.5 * w;
          y += 0.5 * h;
        }

        db::DCplxTrans t = db::DCplxTrans (1.0, a, false, db::DPoint ());

        clear_points ();

        add_point (t * db::DPoint (x - w * 0.5, y - h * 0.5));
        add_point (t * db::DPoint (x - w * 0.5, y + h * 0.5));
        add_point (t * db::DPoint (x + w * 0.5, y + h * 0.5));
        add_point (t * db::DPoint (x + w * 0.5, y - h * 0.5));

        produce_polygon (clear);

      } else if (code == 4) {

        int pol = 0;
        ex.expect (",");
        ex.read (pol);

        if (pol == 0) {
          clear = true;
        } else if (pol == 1) {
          clear = false;
        } else if (pol == 2) {
          clear = !clear;
        } else {
          throw tl::Exception (tl::to_string (QObject::tr ("Invalid exposure code '%d'")), pol);
        }

        ex.expect (",");
        int n = int (read_expr (ex) + 0.5);
        if (n < 1) {
          throw tl::Exception (tl::to_string (QObject::tr ("Invalid point count in outline element in aperture macro")));
        }

        std::vector<db::DPoint> points;

        for (int i = 0; i < n + 1; ++i) {

          ex.expect (",");
          double x = read_expr (ex, true);

          ex.expect (",");
          double y = read_expr (ex, true);

          points.push_back (db::DPoint (x, y));

        }

        ex.expect (",");
        double a = read_expr (ex);

        db::DCplxTrans t = db::DCplxTrans (1.0, a, false, db::DPoint ());

        if (points.size () > 2 && points.front ().sq_distance (points.back ()) < 1e-10) {

          //  closed outline - fill solid 

          clear_points ();

          for (std::vector<db::DPoint>::const_iterator o = points.begin () + 1; o != points.end (); ++o) {
            add_point (t * *o);
          }

          produce_polygon (clear);

        } else if (! points.empty ()) {

          //  open outline - create thin path

          double w = 2.0; // make this variable?

          for (std::vector<db::DPoint>::const_iterator o = points.begin () + 1; o != points.end (); ++o) {

            db::DPoint from = t * o[-1];
            db::DPoint to   = t * o[0];

            db::DPoint p (to - from);
            if (p.sq_distance () > 1e-10) {

              clear_points ();

              p = db::DPoint (p.y(), -p.x()) * (0.5 * w / p.distance ());

              for (int i = 0; i < 2; ++i) {
                add_point (from + p);
                p = db::DPoint (-p.x(), -p.y ());
              }

              for (int i = 0; i < 2; ++i) {
                p = db::DPoint (-p.x(), -p.y ());
                add_point (to + p);
              }

              produce_polygon (clear);

            }

          }

        }

      } else if (code == 5) {

        int pol = 0;
        ex.expect (",");
        ex.read (pol);

        if (pol == 0) {
          clear = true;
        } else if (pol == 1) {
          clear = false;
        } else if (pol == 2) {
          clear = !clear;
        } else {
          throw tl::Exception (tl::to_string (QObject::tr ("Invalid exposure code '%d'")), pol);
        }

        ex.expect (",");
        int n = int (read_expr (ex) + 0.5);
        if (n < 3) {
          throw tl::Exception (tl::to_string (QObject::tr ("Invalid point count in polygon element in aperture macro")));
        }

        ex.expect (",");
        double x = read_expr (ex, true);
        ex.expect (",");
        double y = read_expr (ex, true);
        ex.expect (",");
        double d = read_expr (ex, true);
        ex.expect (",");
        double a0 = read_expr (ex);

        clear_points ();

        for (int i = 0; i < n; ++i) {
          double a = M_PI * 2.0 * double (i) / double (n) + a0 * (M_PI / 180.0);
          add_point (x + 0.5 * d * cos (a), y + 0.5 * d * sin (a));
        }

        produce_polygon (clear);

      } else if (code == 6) {

        ex.expect (",");
        double x = read_expr (ex, true);
        ex.expect (",");
        double y = read_expr (ex, true);
        ex.expect (",");
        double d = read_expr (ex, true);
        ex.expect (",");
        double t = read_expr (ex, true);
        ex.expect (",");
        double g = read_expr (ex, true);
        ex.expect (",");
        int n = int (read_expr (ex) + 0.5);
        ex.expect (",");
        double ct = read_expr (ex, true);
        ex.expect (",");
        double cl = read_expr (ex, true);
        ex.expect (",");
        double a = read_expr (ex);

        db::DCplxTrans tr = db::DCplxTrans (db::DPoint (x, y)) * db::DCplxTrans (1.0, a, false, db::DPoint ()) * db::DCplxTrans (db::DPoint (-x, -y));

        for (int i = 0; i < n; ++i) {
          produce_circle (x, y, d * 0.5 - i * (g + t), false);
          produce_circle (x, y, d * 0.5 - i * (g + t) - t, true);
        }

        clear_points ();
        add_point (tr * db::DPoint (-0.5 * cl, -0.5 * ct));
        add_point (tr * db::DPoint (-0.5 * cl, 0.5 * ct));
        add_point (tr * db::DPoint (0.5 * cl, 0.5 * ct));
        add_point (tr * db::DPoint (0.5 * cl, -0.5 * ct));
        produce_polygon (false);

        clear_points ();
        add_point (tr * db::DPoint (-0.5 * ct, -0.5 * cl));
        add_point (tr * db::DPoint (-0.5 * ct, 0.5 * cl));
        add_point (tr * db::DPoint (0.5 * ct, 0.5 * cl));
        add_point (tr * db::DPoint (0.5 * ct, -0.5 * cl));
        produce_polygon (false);

      } else if (code == 7) {

        ex.expect (",");
        double x = read_expr (ex, true);
        ex.expect (",");
        double y = read_expr (ex, true);
        ex.expect (",");
        double d = read_expr (ex, true);
        ex.expect (",");
        double di = read_expr (ex, true);
        ex.expect (",");
        double ct = read_expr (ex, true);
        ex.expect (",");
        double a = read_expr (ex);

        db::DCplxTrans t = db::DCplxTrans (db::DPoint (x, y)) * db::DCplxTrans (1.0, a, false, db::DPoint ()) * db::DCplxTrans (db::DPoint (-x, -y));

        produce_circle (x, y, d * 0.5, false);
        produce_circle (x, y, di * 0.5, true);

        clear_points ();
        add_point (t * db::DPoint (-0.5 * d, -0.5 * ct));
        add_point (t * db::DPoint (-0.5 * d, 0.5 * ct));
        add_point (t * db::DPoint (0.5 * d, 0.5 * ct));
        add_point (t * db::DPoint (0.5 * d, -0.5 * ct));
        produce_polygon (true);

        clear_points ();
        add_point (t * db::DPoint (-0.5 * ct, -0.5 * d));
        add_point (t * db::DPoint (-0.5 * ct, 0.5 * d));
        add_point (t * db::DPoint (0.5 * ct, 0.5 * d));
        add_point (t * db::DPoint (0.5 * ct, -0.5 * d));
        produce_polygon (true);

      } else if (code == 3) {
        //  end of file - ignore
      } else if (code == 0) {

        //  comment
        while (! ex.at_end () && *ex != '*') {
          ++ex;
        }

      }

      ex.test ("*");

    } 

  }
}

bool 
RS274XMacroAperture::do_produce_linear (const db::DPoint &from, const db::DPoint &to) 
{
  return false;
}

double 
RS274XMacroAperture::read_atom (tl::Extractor &ex)
{
  if (ex.test ("$")) {
    int nvar = 0;
    ex.read (nvar);
    nvar -= 1;
    if (nvar >= 0 && nvar < int (m_parameters.size ())) {
      return m_parameters[nvar];
    } else {
      return 0.0;
    }
  } else if (ex.test ("(")) {
    double d = read_expr (ex);
    ex.expect (")");
    return d;
  } else {
    double d = 0.0;
    ex.read (d);
    return d;
  }
}

double 
RS274XMacroAperture::read_dot_expr (tl::Extractor &ex)
{
  double d = read_atom (ex);
  while (! ex.at_end ()) {
    if (ex.test ("x") || ex.test ("X")) {
      d *= read_atom (ex);
    } else if (ex.test ("/")) {
      d /= read_atom (ex);
    } else {
      break;
    }
  }

  return d;
}

double 
RS274XMacroAperture::read_expr (tl::Extractor &ex, bool length)
{
  double d = read_dot_expr (ex);
  while (! ex.at_end ()) {
    if (ex.test ("+")) {
      d += read_dot_expr (ex);
    } else if (ex.test ("-")) {
      d -= read_dot_expr (ex);
    } else {
      break;
    }
  }

  if (length) {
    d *= m_unit;
  }

  return d;
}

}

