
/*

  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 "dbGDS2ReaderBase.h"
#include "dbGDS2.h"
#include "dbArray.h"

#include "tlException.h"
#include "tlString.h"
#include "tlClassRegistry.h"

namespace db
{

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

/**
 *  @brief A utility class that maps the layers for the proxy cell recovery
 */
class GDS2ReaderLayerMapping
  : public db::ImportLayerMapping
{
public:
  GDS2ReaderLayerMapping (db::GDS2ReaderBase *reader, db::Layout *layout, bool create)
    : mp_reader (reader), mp_layout (layout), m_create (create)
  {
    //  .. nothing yet .. 
  }

  std::pair<bool, unsigned int> map_layer (const db::LayerProperties &lprops)
  {
    //  named layers that are imported from a library are ignored
    if (lprops.is_named ()) {
      return std::make_pair (false, 0);
    } else {
      return mp_reader->open_dl (*mp_layout, LDPair (lprops.layer, lprops.datatype), m_create);
    }
  }

private:
  db::GDS2ReaderBase *mp_reader;
  db::Layout *mp_layout;
  bool m_create;
};

// ---------------------------------------------------------------
//  GDS2ReaderBase

GDS2ReaderBase::GDS2ReaderBase ()
  : m_dbu (0.001), 
    m_dbuu (1.0), 
    m_create_layers (true), 
    m_read_texts (true),
    m_read_properties (true),
    m_allow_multi_xy_records (false),
    m_box_mode (0)
{
  // .. nothing yet ..
}

GDS2ReaderBase::~GDS2ReaderBase ()
{
  // .. nothing yet ..
}

const LayerMap &
GDS2ReaderBase::basic_read (db::Layout &layout, const LoadLayoutOptions &options, bool allow_multi_xy_records, unsigned int box_mode) 
{
  m_layer_map = options.layer_map ();
  m_layer_map.prepare (layout);
  m_read_texts = options.enable_text_objects ();
  m_read_properties = options.enable_properties ();

  m_allow_multi_xy_records = allow_multi_xy_records;
  m_box_mode = box_mode;
  m_create_layers = options.create_other_layers ();

  layout.start_changes ();
  do_read (layout);
  layout.end_changes ();

  return m_layer_map;
}

void
GDS2ReaderBase::finish_element ()
{
  while (true) {

    short rec_id = get_record ();

    if (rec_id == sENDEL) {
      break;
    } else if (rec_id == sPROPATTR) {
      //  skip this record
    } else if (rec_id == sPROPVALUE) {
      //  skip this record
    } else if (rec_id == sTEXT || rec_id == sPATH || rec_id == sBOUNDARY || rec_id == sBOX || 
               rec_id == sAREF || rec_id == sSREF || rec_id == sENDSTR) {
      unget_record (rec_id);
      warn (tl::to_string (QObject::tr ("ENDEL record expected - assuming missing ENDEL")));
      break;
    } else {
      error (tl::to_string (QObject::tr ("ENDEL, PROPATTR or PROPVALUE record expected")));
    }

  } 
}


std::pair <bool, db::properties_id_type> 
GDS2ReaderBase::finish_element (db::PropertiesRepository &rep)
{
  bool any = false;
  long attr = 0;

  db::PropertiesRepository::properties_set properties;

  while (true) {

    short rec_id = get_record ();

    if (rec_id == sENDEL) {
      break;
    } else if (rec_id == sPROPATTR) {
      attr = long (get_ushort ());
    } else if (rec_id == sPROPVALUE) {

      const char *value = get_string ();
      if (m_read_properties) {
        properties.insert (std::make_pair (rep.prop_name_id (tl::Variant (attr)), 
                                           tl::Variant (value)));

        any = true;
      }

    } else if (rec_id == sTEXT || rec_id == sPATH || rec_id == sBOUNDARY || rec_id == sBOX || 
               rec_id == sAREF || rec_id == sSREF || rec_id == sENDSTR) {
      unget_record (rec_id);
      warn (tl::to_string (QObject::tr ("ENDEL record expected - assuming missing ENDEL")));
      break;
    } else {
      error (tl::to_string (QObject::tr ("ENDEL, PROPATTR or PROPVALUE record expected")));
    }

  } 

  if (any) {
    return std::make_pair (true, rep.properties_id (properties));
  } else {
    return std::make_pair (false, 0);
  }
}


std::pair <bool, unsigned int> 
GDS2ReaderBase::open_dl (db::Layout &layout, const LDPair &dl, bool create) 
{
  std::pair<bool, unsigned int> ll = m_layer_map.logical (dl);
  if (ll.first) {

    return ll;

  } else if (! create) {

    //  layer not mapped and no layer create is requested
    return ll;

  } else {

    //  and create the layer
    db::LayerProperties lp;
    lp.layer = dl.layer;
    lp.datatype = dl.datatype;

    unsigned int ll = layout.insert_layer (lp);
    m_layer_map.map (dl, ll, lp);

    return std::make_pair (true, ll);

  }
}

inline db::Point 
pt_conv (const GDS2XY &p) 
{
  //  KLUDGE: this can be done more efficiently ..
  int x = (int (p.x[0]) << 24) | (int (p.x[1]) << 16) | (int (p.x[2]) << 8) | int (p.x[3]);
  int y = (int (p.y[0]) << 24) | (int (p.y[1]) << 16) | (int (p.y[2]) << 8) | int (p.y[3]);
  return db::Point (x, y);
}

inline bool 
eq_x (const GDS2XY &a, const GDS2XY &b)
{
  /// Re-cast pointer to char[4] as pointer to 32bit int for faster comparison
  return *(( int*)a.x) == *(( int*)b.x);
}

inline bool 
eq_y (const GDS2XY &a, const GDS2XY &b)
{
  /// Re-cast pointer to char[4] as pointer to 32bit int for faster comparison
  return *(( int*)a.y) == *(( int*)b.y);
}

void 
GDS2ReaderBase::do_read (db::Layout &layout) 
{
  m_cellname = "";
  m_libname = "";

  //  read header
  if (get_record () != sHEADER) {
    error (tl::to_string (QObject::tr ("HEADER record expected")));
  }
  if (get_record () != sBGNLIB) {
    error (tl::to_string (QObject::tr ("BGNLIB record expected")));
  }

  unsigned int mod_time[6] = { 0, 0, 0, 0, 0, 0 };
  unsigned int access_time[6] = { 0, 0, 0, 0, 0, 0 };
  get_time (mod_time, access_time);
  add_meta_info (MetaInfo ("mod_time", tl::to_string (QObject::tr ("Modification Time")), tl::sprintf ("%d/%d/%d %d:%02d:%02d", mod_time[1], mod_time[2], mod_time[0], mod_time[3], mod_time[4], mod_time[5]))); 
  add_meta_info (MetaInfo ("access_time", tl::to_string (QObject::tr ("Access Time")), tl::sprintf ("%d/%d/%d %d:%02d:%02d", access_time[1], access_time[2], access_time[0], access_time[3], access_time[4], access_time[5]))); 

  long attr = 0;
  db::PropertiesRepository::properties_set layout_properties;

  //  read until 
  short rec_id = 0;
  do {
    rec_id = get_record ();
    if (rec_id == sLIBDIRSIZE ||
        rec_id == sSRFNAME ||
        rec_id == sREFLIBS ||
        rec_id == sFONTS ||
        rec_id == sATTRTABLE ||
        rec_id == sGENERATIONS ||
        rec_id == sFORMAT ||
        rec_id == sMASK ||
        rec_id == sENDMASKS) {

      //  OK and overread
      
    } else if (rec_id == sLIBNAME) {

      m_libname = get_string ();

    } else if (rec_id == sBGNSTR || rec_id == sENDLIB) {

      //  start with cells or finish (for empty file)
      unget_record (rec_id);
      break;

    } else if (rec_id == sPROPATTR) {

      attr = long (get_ushort ());

    } else if (rec_id == sPROPVALUE) {

      const char *value = get_string ();
      if (m_read_properties) {
        layout_properties.insert (std::make_pair (layout.properties_repository ().prop_name_id (tl::Variant (attr)), tl::Variant (value)));
      }

    } else if (rec_id == sUNITS) {

      //  get units
      double dbuu = get_double ();
      double dbum = get_double ();
      
      add_meta_info (MetaInfo ("dbuu", tl::to_string (QObject::tr ("Database unit in user units")), tl::to_string (dbuu)));
      add_meta_info (MetaInfo ("dbum", tl::to_string (QObject::tr ("Database unit in meter")), tl::to_string (dbum)));
      add_meta_info (MetaInfo ("libname", tl::to_string (QObject::tr ("Library name")), m_libname));

      m_dbuu = dbuu;
      m_dbu = dbum * 1e6; /*in micron*/
      layout.dbu (m_dbu);

    } else {
      error (tl::to_string (QObject::tr ("Invalid record or data type")));
    }

  } while (true);

  //  set the layout properties
  if (! layout_properties.empty ()) {
    layout.prop_id (layout.properties_repository ().properties_id (layout_properties));
  }

  //  this container has been found to grow quite a lot.
  //  using a list instead of a vector should make this more efficient.
  tl::vector<db::CellInstArray> instances;
  tl::vector<db::CellInstArrayWithProperties> instances_with_props;

  //  prepare a string vector for the context information
  m_context_info.clear ();

  bool first_cell = true;

  //  get cells
  while ((rec_id = get_record ()) == sBGNSTR) {

    progress_checkpoint ();

    //  erase current instance list 
    instances.erase (instances.begin (), instances.end ());
    instances_with_props.erase (instances_with_props.begin (), instances_with_props.end ());

    if (get_record () != sSTRNAME) {
      error (tl::to_string (QObject::tr ("STRNAME record expected")));
    }

    get_string (m_cellname);

    //  if the first cell is the dummy cell containing the context informations
    //  read this cell in a special way and store the context informations separately.
    if (first_cell && m_cellname == "$$$CONTEXT_INFO$$$") {

      read_context_info_cell ();

    } else {

      db::cell_index_type cell_index;

      std::pair<bool, db::cell_index_type> c = layout.cell_by_name (m_cellname.c_str ()); 
      if (c.first) {
        //  cell already there: just add shapes (cell might have been created through forward reference)
        cell_index = c.second;
        //  remove "ghost cell" state
        layout.cell (cell_index).set_ghost_cell (false);
      } else {
        cell_index = layout.add_cell (m_cellname.c_str ());
      }

      db::Cell *cell = &layout.cell (cell_index);

      std::map <tl::string, std::vector <std::string> >::const_iterator ctx = m_context_info.find (m_cellname);
      if (ctx != m_context_info.end ()) {
        GDS2ReaderLayerMapping layer_mapping (this, &layout, m_create_layers);
        if (layout.recover_proxy_as (cell_index, ctx->second.begin (), ctx->second.end (), &layer_mapping)) {
          //  ignore everything in that cell since it is created by the import:
          cell = 0;
        }
      }
      
      long attr = 0;
      db::PropertiesRepository::properties_set cell_properties;

      //  read cell content
      while ((rec_id = get_record ()) != sENDSTR) { 

        progress_checkpoint ();

        if (cell == 0) {

          //  ignore everything in proxy cells: these are created from the libraries or PCell's.

        } else if (rec_id == sPROPATTR) {

          attr = long (get_ushort ());

        } else if (rec_id == sPROPVALUE) {

          const char *value = get_string ();
          if (m_read_properties) {
            cell_properties.insert (std::make_pair (layout.properties_repository ().prop_name_id (tl::Variant (attr)), tl::Variant (value)));
          }

        } else if (rec_id == sBOUNDARY) {

          read_boundary (layout, *cell, false);

        } else if (rec_id == sPATH) {

          read_path (layout, *cell);

        } else if (rec_id == sSREF || rec_id == sAREF) {

          bool array = (rec_id == sAREF);
          read_ref (layout, *cell, array, instances, instances_with_props);

        } else if (rec_id == sTEXT) {

          read_text (layout, *cell);

        } else if (rec_id == sBOX) {

          if (m_box_mode == 1) {
            read_box (layout, *cell);
          } else if (m_box_mode == 2) {
            read_boundary (layout, *cell, true);
          } else if (m_box_mode == 3) {
            error (tl::to_string (QObject::tr ("BOX record encountered (reader is configured to produce an error in this case)")));
          } else {
            while (get_record () != sENDEL) { }
          }

        } else if (rec_id == sNODE) {

          //  NODE records are ignored.
          while (get_record () != sENDEL) { }

        } else {
          error (tl::to_string (QObject::tr ("Invalid record or data type")));
        }
      
      }

      //  insert all instances collected
      if (! instances.empty ()) {
        cell->insert (instances.begin (), instances.end ());
      }
      if (! instances_with_props.empty ()) {
        cell->insert (instances_with_props.begin (), instances_with_props.end ());
      }

      //  set the cell properties
      if (! cell_properties.empty ()) {
        cell->prop_id (layout.properties_repository ().properties_id (cell_properties));
      }

    }

    m_cellname = "";
    first_cell = false;

  }

  //  check, if the last record is a ENDLIB
  if (rec_id != sENDLIB) {
    error (tl::to_string (QObject::tr ("ENDLIB record expected")));
  }
}

void
GDS2ReaderBase::read_context_info_cell ()
{
  short rec_id = 0;

  //  read cell content
  while ((rec_id = get_record ()) != sENDSTR) { 

    progress_checkpoint ();

    if (rec_id == sSREF) {

      do {
        rec_id = get_record ();
      } while (rec_id == sELFLAGS || rec_id == sPLEX);
      if (rec_id != sSNAME) {
        error (tl::to_string (QObject::tr ("SNAME record expected")));
      }

      std::string cn = get_string ();

      rec_id = get_record ();
      while (rec_id == sSTRANS || rec_id == sANGLE || rec_id == sMAG) {
        rec_id = get_record ();
      }
      if (rec_id != sXY) {
        error (tl::to_string (QObject::tr ("XY record expected")));
      }

      std::vector <std::string> &strings = m_context_info.insert (std::make_pair (cn, std::vector <std::string> ())).first->second;

      size_t attr = 0;

      while (true) {

        rec_id = get_record ();

        if (rec_id == sENDEL) {
          break;
        } else if (rec_id == sPROPATTR) {
          attr = size_t (get_ushort ());
        } else if (rec_id == sPROPVALUE) {

          if (strings.size () <= attr) {
            strings.resize (attr + 1, std::string ());
          }
          strings [attr] = get_string ();

        } else {
          error (tl::to_string (QObject::tr ("ENDEL, PROPATTR or PROPVALUE record expected")));
        }

      } 

    } else {
      error (tl::to_string (QObject::tr ("Invalid record inside a context info cell")));
    }
  
  }
}

void 
GDS2ReaderBase::read_boundary (db::Layout &layout, db::Cell &cell, bool from_box_record)
{
  LDPair ld; 
  short rec_id = 0;

  do {
    rec_id = get_record ();
  } while (rec_id == sELFLAGS || rec_id == sPLEX);
  if (rec_id != sLAYER) {
    error (tl::to_string (QObject::tr ("LAYER record expected")));
  }
  ld.layer = get_ushort ();

  rec_id = get_record ();
  if (from_box_record) {
    if (rec_id != sBOXTYPE) {
      error (tl::to_string (QObject::tr ("BOXTYPE record expected")));
    }
  } else {
    if (rec_id != sDATATYPE) {
      error (tl::to_string (QObject::tr ("DATATYPE record expected")));
    }
  }

  ld.datatype = get_ushort ();

  if (get_record () != sXY) {
    error (tl::to_string (QObject::tr ("XY record expected")));
  }

  unsigned int xy_length = 0;
  GDS2XY *xy_data = get_xy_data (xy_length);

  std::pair<bool, unsigned int> ll = open_dl (layout, ld, m_create_layers);
  if (ll.first) {

    //  create a box object if possible
    GDS2XY *xy = xy_data;
    if ((xy_length == 4 || 
         (xy_length == 5 && pt_conv (xy[4]) == pt_conv (xy[0]))) && 
        ((eq_x (xy[0], xy[1]) && eq_x (xy[2], xy[3]) && 
          eq_y (xy[1], xy[2]) && eq_y (xy[0], xy[3])) ||
         (eq_x (xy[1], xy[2]) && eq_x (xy[0], xy[3]) && 
          eq_y (xy[0], xy[1]) && eq_y (xy[2], xy[3])))) {

      //  we can create a box object:
      db::Point p1 = pt_conv (*xy++);
      db::Point p2 = p1;
       
      while (xy < xy_data + 4) {
        db::Point p (pt_conv (*xy++));
        if (p.x () < p1.x ()) {
          p1.set_x (p.x ());
        }
        if (p.y () < p1.y ()) {
          p1.set_y (p.y ());
        }
        if (p.x () > p2.x ()) {
          p2.set_x (p.x ());
        }
        if (p.y () > p2.y ()) {
          p2.set_y (p.y ());
        }
      }

      std::pair<bool, db::properties_id_type> pp = finish_element (layout.properties_repository ());
      if (pp.first) {
        cell.shapes (ll.second).insert (db::BoxWithProperties (db::Box (p1, p2), pp.second));
      } else {
        cell.shapes (ll.second).insert (db::Box (p1, p2));
      }

    } else {

      //  convert the GDS2 record into the polygon.
      db::SimplePolygon poly;

      //  Try to detect Multi-XY records. A good indication may be a huge point count.
      if (xy_length > 2000) {

        std::vector<db::Point> all_points;
        all_points.reserve (xy_length * 2); // allocate some (hopefully enough) elements

        while (true) {

          for (GDS2XY *xy = xy_data; xy < xy_data + xy_length; ++xy) {
            all_points.push_back (pt_conv (*xy));
          }

          if ((rec_id = get_record ()) == sXY) {
            xy_data = get_xy_data (xy_length);
            if (! m_allow_multi_xy_records) {
              error (tl::to_string (QObject::tr ("Multiple XY records detected on BOUNDARY element (reader is configured not to allow this)")));
            }
          } else {
            unget_record (rec_id);
            break;
          }

        } 

        poly.assign_hull (all_points.begin (), all_points.end ());

      } else {
        poly.assign_hull (xy_data, xy_data + xy_length, pt_conv);
      }

      if (poly.hull ().size () < 3) {
        warn (tl::to_string (QObject::tr ("BOUNDARY with less than 3 points ignored")));
        finish_element ();
      } else {
        //  this will copy the polyon:
        std::pair<bool, db::properties_id_type> pp = finish_element (layout.properties_repository ());
        if (pp.first) {
          cell.shapes (ll.second).insert (db::SimplePolygonRefWithProperties (db::SimplePolygonRef (poly, layout.shape_repository ()), pp.second));
        } else {
          cell.shapes (ll.second).insert (db::SimplePolygonRef (poly, layout.shape_repository ()));
        }
      }

    }

  } else {

    while ((rec_id = get_record ()) == sXY) { 
      // read over multi-XY records
    }
    unget_record (rec_id);

    finish_element ();

  }
}

void 
GDS2ReaderBase::read_path (db::Layout &layout, db::Cell &cell)
{
  LDPair ld; 
  short rec_id = 0;

  do {
    rec_id = get_record ();
  } while (rec_id == sELFLAGS || rec_id == sPLEX);
  if (rec_id != sLAYER) {
    error (tl::to_string (QObject::tr ("LAYER record expected")));
  }
  ld.layer = get_ushort ();
  if (get_record () != sDATATYPE) {
    error (tl::to_string (QObject::tr ("DATATYPE record expected")));
  }
  ld.datatype = get_ushort ();
    
  rec_id = get_record ();

  short type = 0; 
  if (rec_id == sPATHTYPE) {
    type = get_ushort (); 
    rec_id = get_record ();
  }

  if (type != 0 && type != 1 && type != 2 && type != 4) {
    warn (tl::to_string (QObject::tr ("Unsupported PATHTYPE")));
    type = 0;
  }

  db::Coord w = 0;
  if (rec_id == sWIDTH) {
    w = get_int ();
    rec_id = get_record ();
  }

  db::Coord bgn_ext = 0;
  db::Coord end_ext = 0;

  if (rec_id == sBGNEXTN) {
    bgn_ext = get_int ();
    rec_id = get_record ();
  } else {
    if (type == 2 || type == 1) {
      bgn_ext = w / 2;
    } 
  }

  if (rec_id == sENDEXTN) {
    end_ext = get_int ();
    rec_id = get_record ();
  } else {
    if (type == 2 || type == 1) {
      end_ext = w / 2;
    } 
  }

  if (rec_id != sXY) {
    error (tl::to_string (QObject::tr ("XY record expected")));
  }

  unsigned int xy_length = 0;
  GDS2XY *xy_data = get_xy_data (xy_length);

  std::pair<bool, unsigned int> ll = open_dl (layout, ld, m_create_layers);
  if (ll.first) {

    //  this will copy the path:
    db::Path path;
    path.width (w);
    path.extensions (bgn_ext, end_ext);
    path.assign (xy_data, xy_data + xy_length, pt_conv);
    path.round (type == 1);

    if (path.points () < 1) {
      warn (tl::to_string (QObject::tr ("PATH with less than one point ignored")));
      finish_element ();
    } else {
      if (path.points () < 2 && type != 1) {
        warn (tl::to_string (QObject::tr ("PATH with less than two points encountered - interpretation may be different in other tools")));
      }
      std::pair<bool, db::properties_id_type> pp = finish_element (layout.properties_repository ());
      if (pp.first) {
        cell.shapes (ll.second).insert (db::PathRefWithProperties (db::PathRef (path, layout.shape_repository ()), pp.second));
      } else {
        cell.shapes (ll.second).insert (db::PathRef (path, layout.shape_repository ()));
      }
    }

  } else {
    finish_element ();
  }
}

void 
GDS2ReaderBase::read_text (db::Layout &layout, db::Cell &cell)
{
  LDPair ld; 
  short rec_id = 0;

  do {
    rec_id = get_record ();
  } while (rec_id == sELFLAGS || rec_id == sPLEX);
  if (rec_id != sLAYER) {
    error (tl::to_string (QObject::tr ("LAYER record expected")));
  }
  ld.layer = get_ushort ();
  if (get_record () != sTEXTTYPE) {
    error (tl::to_string (QObject::tr ("DATATYPE record expected")));
  }
  ld.datatype = get_ushort ();

  std::pair<bool, unsigned int> ll (false, 0);

  if (m_read_texts) {
    ll = open_dl (layout, ld, m_create_layers);
  }

  rec_id = get_record ();

  db::HAlign ha = db::NoHAlign;
  db::VAlign va = db::NoVAlign;
  db::Font font = db::NoFont;

  if (rec_id == sPRESENTATION) {
    short p = get_short ();
    ha = db::HAlign (p & 3);
    va = db::VAlign ((p >> 2) & 3);
    // HINT: currently we don't read the font since the font is not well standardized ..
    // font = (db::Font) ((p >> 4) & 0xfff);
    rec_id = get_record ();
  }

  if (rec_id == sPATHTYPE) {
    rec_id = get_record ();
  }

  if (rec_id == sWIDTH) {
    rec_id = get_record ();
  }

  bool mirror = false;
  int angle = 0;
  db::Coord size = 0;

  while (rec_id == sSTRANS || rec_id == sMAG || rec_id == sANGLE) {

    if (rec_id == sSTRANS) {

      short f = get_short ();
      if ((f & 0x8000) != 0) {
        mirror = true;
      }

    } else if (rec_id == sMAG) {

      size = db::coord_traits<db::Coord>::rounded (get_double () / m_dbuu);

    } else if (rec_id == sANGLE) {

      if (ll.first) {
        double aorg = get_double ();
        double a = aorg / 90.0;
        if (a < -4 || a > 4) {
          warn (tl::sprintf (tl::to_string (QObject::tr ("Invalid text rotation angle (%g is less than -360 or larger than 360)")), aorg));
        }
        angle = int (a < 0 ? (a - 0.5) : (a + 0.5));
        if (fabs (double (angle) - a) > 1e-9) {
          warn (tl::sprintf (tl::to_string (QObject::tr ("Invalid text rotation angle (%g is not a multiple of 90)")), aorg));
        }
        while (angle < 0) {
          angle += 4;
        }
        while (angle >= 4) {
          angle -= 4;
        }
      }

    }

    rec_id = get_record ();
      
  }

  if (rec_id != sXY) {
    error (tl::to_string (QObject::tr ("XY record expected")));
  }

  unsigned int xy_length = 0;
  GDS2XY *xy_data = get_xy_data (xy_length);
  if (xy_length == 0) {
    error (tl::to_string (QObject::tr ("No point in XY record for text")));
  } else if (xy_length > 1) {
    warn (tl::to_string (QObject::tr ("More than one point in XY record for text")));
  }

  db::Trans t (angle, mirror, pt_conv (xy_data [0]));

  if (get_record () != sSTRING) {
    error (tl::to_string (QObject::tr ("STRING record expected")));
  }

  if (ll.first) {

    //  Create the text
    db::Text text (get_string (), t, size, font, ha, va);

    std::pair<bool, db::properties_id_type> pp = finish_element (layout.properties_repository ());
    if (pp.first) {
      cell.shapes (ll.second).insert (db::TextRefWithProperties (db::TextRef (text, layout.shape_repository ()), pp.second));
    } else {
      cell.shapes (ll.second).insert (db::TextRef (text, layout.shape_repository ()));
    }

  } else {
    finish_element ();
  }
}

void 
GDS2ReaderBase::read_box (db::Layout &layout, db::Cell &cell)
{
  LDPair ld; 
  short rec_id = 0;

  do {
    rec_id = get_record ();
  } while (rec_id == sELFLAGS || rec_id == sPLEX);
  if (rec_id != sLAYER) {
    error (tl::to_string (QObject::tr ("LAYER record expected")));
  }
  ld.layer = get_ushort ();
  if (get_record () != sBOXTYPE) {
    error (tl::to_string (QObject::tr ("DATATYPE record expected")));
  }
  ld.datatype = get_ushort ();

  std::pair<bool, unsigned int> ll = open_dl (layout, ld, m_create_layers);

  if (get_record () != sXY) {
    error (tl::to_string (QObject::tr ("XY record expected")));
  }

  unsigned int xy_length = 0;
  GDS2XY *xy_data = get_xy_data (xy_length);

  if (ll.first) {

    GDS2XY *xy = xy_data;
    db::Box box;
    while (xy < xy_data + xy_length) {
      box += pt_conv (*xy++);
    }

    std::pair<bool, db::properties_id_type> pp = finish_element (layout.properties_repository ());
    if (! box.empty ()) {
      if (pp.first) {
        cell.shapes (ll.second).insert (db::BoxWithProperties (box, pp.second));
      } else {
        cell.shapes (ll.second).insert (box);
      }
    }

  } else {
    finish_element ();
  }
}

void 
GDS2ReaderBase::read_ref (db::Layout &layout, db::Cell &cell, bool array, tl::vector<db::CellInstArray> &instances, tl::vector<db::CellInstArrayWithProperties> &instances_with_props)
{
  short rec_id = 0;

  do {
    rec_id = get_record ();
  } while (rec_id == sELFLAGS || rec_id == sPLEX);
  if (rec_id != sSNAME) {
    error (tl::to_string (QObject::tr ("SNAME record expected")));
  }

  db::cell_index_type ci;

  {
    //  obtain cell or create new: "cn" is not valid beyond the 
    //  scope of the get_string() call
    const char *cn = get_string ();
    std::pair<bool, db::cell_index_type> c = layout.cell_by_name (cn); 
    if (c.first) {
      //  cell already there: just add shapes (cell might have been created through forward reference)
      ci = c.second;
    } else {
      ci = layout.add_cell (cn);
      //  mark this cell a "ghost cell" until it's actually read
      layout.cell (ci).set_ghost_cell (true);
    }
  }

  bool mirror = false;
  int angle = 0;
  double angle_deg = 0.0;
  double mag = 1.0;
  bool is_mag = false;

  rec_id = get_record ();

  while (rec_id == sSTRANS || rec_id == sMAG || rec_id == sANGLE) {

    if (rec_id == sSTRANS) {
      short f = get_short ();
      if ((f & 0x8000) != 0) {
        mirror = true;
      }
      if ((f & (4 | 2)) != 0) {
        warn (tl::to_string (QObject::tr ("Absolute transformations are not supported")));
      }  
    } else if (rec_id == sMAG) {
      mag = get_double ();
      if (fabs (mag - 1.0) > 1e-9) { 
        is_mag = true;
      }
    } else if (rec_id == sANGLE) {
      angle_deg = get_double ();
      double a = angle_deg / 90.0;
      if (a < -4 || a > 4) {
        warn (tl::sprintf (tl::to_string (QObject::tr ("Invalid rotation angle (%g is less than -360 or larger than 360)")), angle_deg));
      }
      angle = int (a < 0 ? (a - 0.5) : (a + 0.5));
      if (fabs (double (angle) - a) > 1e-9) {
        angle = -1; // indicates arbitrary orientation. Take angle_deg instead
      } else {
        if (angle < 0) {
          angle += ((4 - 1) - angle) & ~(4 - 1);
        }
        angle = angle % 4;
      }
    }

    rec_id = get_record ();

  }

  if (array) {

    //  Array reference
    if (rec_id != sCOLROW) {
      error (tl::to_string (QObject::tr ("COLROW record expected")));
    }

    int cols = get_ushort ();
    int rows = get_ushort ();

    //  Array reference
    if (get_record () != sXY) {
      error (tl::to_string (QObject::tr ("XY record expected")));
    }

    //  Create the instance
    unsigned int xy_length = 0;
    GDS2XY *xy_data = get_xy_data (xy_length);
    if (xy_length < 3) {
      error (tl::to_string (QObject::tr ("Too few points in XY record for AREF")));
    } else if (xy_length > 3) {
      warn (tl::to_string (QObject::tr ("More than three points in XY record for AREF")));
    }

    //  Create the instance
    db::Point xy = pt_conv (xy_data [0]);
    db::Point c = pt_conv (xy_data [1]) - xy;
    db::Point r = pt_conv (xy_data [2]) - xy;

    std::pair<bool, db::properties_id_type> pp = finish_element (layout.properties_repository ());

    bool split_cols = false, split_rows = false;

    if (cols > 1 && (c.x () % cols != 0 || c.y () % cols != 0)) {
      warn (tl::to_string (QObject::tr ("Off-grid AREF column vector - AREF will be split into subarrays to preserve locations")));
      split_cols = true;
    }
    if (rows > 1 && (r.x () % rows != 0 || r.y () % rows != 0)) {
      warn (tl::to_string (QObject::tr ("Off-grid AREF row vector - AREF will be split into subarrays to preserve locations")));
      split_rows = true;
    }

    if (split_cols || split_rows) {

      cols = std::max (1, cols);
      rows = std::max (1, rows);

      db::DPoint cd (db::DPoint (c) * (1.0 / double (cols)));
      db::DPoint rd (db::DPoint (r) * (1.0 / double (rows)));

      c = db::Point::from_double (cd);
      r = db::Point::from_double (rd);

      int ic = 0;
      while (ic < cols) {
         
        int ic0 = ic;

        if (! split_cols) {
          ic = cols;
        } else {

          db::DPoint p1d = cd * double (ic);
          db::Point p1 = db::Point::from_double (p1d);

          do {
            ++ic;
            p1d += cd;
            p1 += c;
          } while (ic < cols && fabs (p1d.x () - p1.x ()) < 0.5 && fabs (p1d.y () - p1.y ()) < 0.5);

        }

        int ir = 0;
        while (ir < rows) {
         
          int ir0 = ir;

          if (! split_rows) {
            ir = rows;
          } else {

            db::DPoint p2d = rd * double (ir);
            db::Point p2 = db::Point::from_double (p2d);

            do { 
              ++ir;
              p2d += rd;
              p2 += r;
            } while (ir < rows && fabs(p2d.x () - p2.x ()) < 0.5 && fabs(p2d.y () - p2.y ()) < 0.5);

          }

          db::Point p = xy + db::Point::from_double (cd * double (ic0) + rd * double (ir0));

          //  insert the cell array
          db::CellInstArray inst;

          if (is_mag || angle < 0) {
            inst = db::CellInstArray (db::CellInst (ci), 
                                      db::CplxTrans (mag, angle_deg, mirror, p), r, c, ir - ir0, ic - ic0);
          } else {
            inst = db::CellInstArray (db::CellInst (ci), 
                                      db::Trans (angle, mirror, p), r, c, ir - ir0, ic - ic0);
          }

          if (pp.first) {
            instances_with_props.push_back (db::CellInstArrayWithProperties (inst, pp.second));
          } else {
            instances.push_back (inst);
          }

        }

      }

    } else {

      if (cols > 1) {
        c = db::Point (c.x () / cols, c.y () / cols);
      }
      if (rows > 1) {
        r = db::Point (r.x () / rows, r.y () / rows);
      }

      //  insert the cell array
      db::CellInstArray inst;

      if (is_mag || angle < 0) {
        inst = db::CellInstArray (db::CellInst (ci), 
                                  db::CplxTrans (mag, angle_deg, mirror, xy), r, c, rows, cols);
      } else {
        inst = db::CellInstArray (db::CellInst (ci), 
                                  db::Trans (angle, mirror, xy), r, c, rows, cols);
      }

      if (pp.first) {
        instances_with_props.push_back (db::CellInstArrayWithProperties (inst, pp.second));
      } else {
        instances.push_back (inst);
      }

    }

  } else {

    //  Single reference
    if (rec_id != sXY) {
      error (tl::to_string (QObject::tr ("XY record expected")));
    }

    //  Create the instance
    unsigned int xy_length = 0;
    GDS2XY *xy_data = get_xy_data (xy_length);
    if (xy_length < 1) {
      error (tl::to_string (QObject::tr ("Too few points in XY record for SREF")));
    } else if (xy_length > 1) {
      warn (tl::to_string (QObject::tr ("More than one point in XY record for SREF")));
    }

    //  Create the instance
    db::Point xy = pt_conv (xy_data [0]);

    db::CellInstArray inst;

    if (is_mag || angle < 0) {
      inst = db::CellInstArray (db::CellInst (ci), db::CplxTrans (mag, angle_deg, mirror, xy));
    } else {
      inst = db::CellInstArray (db::CellInst (ci), db::Trans (angle, mirror, xy));
    }

    std::pair<bool, db::properties_id_type> pp = finish_element (layout.properties_repository ());
    if (pp.first) {
      instances_with_props.push_back (db::CellInstArrayWithProperties (inst, pp.second));
    } else {
      instances.push_back (inst);
    }

  }  
}


}

