
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2018 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 "dbOASISReader.h"
#include "dbCommonReader.h"
#include "dbStream.h"
#include "dbObjectWithProperties.h"
#include "dbArray.h"
#include "dbStatic.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 OASISReaderLayerMapping
  : public db::ImportLayerMapping
{
public:
  OASISReaderLayerMapping (db::OASISReader *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::OASISReader *mp_reader;
  db::Layout *mp_layout;
  bool m_create;
};

// ---------------------------------------------------------------
//  OASISReader

OASISReader::OASISReader (tl::InputStream &s)
  : m_stream (s), 
    m_progress (tl::to_string (QObject::tr ("Reading OASIS file")), 10000),
    m_dbu (0.001),
    m_expect_strict_mode (-1),
    mm_repetition (this, "repetition"),
    mm_placement_cell (this, "placement-cell"),
    mm_placement_x (this, "playcement-x"),
    mm_placement_y (this, "playcement-y"),
    mm_layer (this, "layer"),
    mm_datatype (this, "datatype"),
    mm_textlayer (this, "textlayer"),
    mm_texttype (this, "texttype"),
    mm_text_x (this, "text-x"),
    mm_text_y (this, "text-y"),
    mm_text_string (this, "text-string"),
    mm_text_string_id (this, "text-string-id"),
    mm_geometry_x (this, "geometry-x"),
    mm_geometry_y (this, "geometry-y"),
    mm_geometry_w (this, "geometry-w"),
    mm_geometry_h (this, "geometry-h"),
    mm_polygon_point_list (this, "polygon-point-list"),
    mm_path_halfwidth (this, "path-halfwidth"),
    mm_path_start_extension (this, "path-start-extension"),
    mm_path_end_extension (this, "path-end-extension"),
    mm_path_point_list (this, "path-point-list"),
    mm_ctrapezoid_type (this, "ctrapezoid-type"),
    mm_circle_radius (this, "circle-radius"),
    mm_last_property_name (this, "last-property-name"),
    mm_last_property_is_sprop (this, "last-property-is-stdprop"),
    mm_last_value_list(this, "last-value-list"),
    m_create_layers (false),
    m_read_texts (true),
    m_read_properties (true),
    m_read_all_properties (false),
    m_s_gds_property_name_id (0),
    m_klayout_context_property_name_id (0)
{
  m_progress.set_format (tl::to_string (QObject::tr ("%.0f MB")));
  m_progress.set_unit (1024 * 1024);
  m_first_cellname = 0;
  m_first_propname = 0;
  m_first_propstring = 0;
  m_first_textstring = 0;
  m_first_layername = 0;
  m_in_table = NotInTable;
  m_table_cellname = 0;
  m_table_propname = 0;
  m_table_propstring = 0;
  m_table_textstring = 0;
  m_table_layername = 0;
  m_table_start = 0;
}

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

const LayerMap &
OASISReader::read (db::Layout &layout, const db::LoadLayoutOptions &options)
{
  db::OASISReaderOptions oasis_options = options.get_options<db::OASISReaderOptions> ();
  db::CommonReaderOptions common_options = options.get_options<db::CommonReaderOptions> ();

  m_layer_map = common_options.layer_map;
  m_layer_map.prepare (layout);
  m_layers_created.clear ();
  m_read_texts = common_options.enable_text_objects;
  m_read_properties = common_options.enable_properties;
  m_create_layers = common_options.create_other_layers;
  m_read_all_properties = oasis_options.read_all_properties;
  m_expect_strict_mode = oasis_options.expect_strict_mode;

  layout.start_changes ();
  try {
    do_read (layout);
    layout.end_changes ();
  } catch (...) {
    layout.end_changes ();
    throw;
  }

  return m_layer_map;
}

const LayerMap &
OASISReader::read (db::Layout &layout)
{
  return read (layout, db::LoadLayoutOptions ());
}

inline long long 
OASISReader::get_long_long ()
{
  unsigned long long u = get_ulong_long ();
  if ((u & 1) != 0) {
    return -(long long) (u >> 1);
  } else {
    return (long long) (u >> 1);
  }
}

inline unsigned long long 
OASISReader::get_ulong_long ()
{
  unsigned long long v = 0;
  unsigned long long vm = 1;
  char c;
  
  do {
    unsigned char *b = (unsigned char *) m_stream.get (1);
    if (! b) {
      error (tl::to_string (QObject::tr ("Unexpected end-of-file")));
      return 0;
    }
    c = *b;
    if (vm > std::numeric_limits <unsigned long long>::max () / 128 && 
        (unsigned long long) (c & 0x7f) > (std::numeric_limits <unsigned long long>::max () / vm)) {
      error (tl::to_string (QObject::tr ("Unsigned long value overflow")));
    }
    v += (unsigned long long) (c & 0x7f) * vm;
    vm <<= 7;
  } while ((c & 0x80) != 0);

  return v;
}

inline long 
OASISReader::get_long ()
{
  unsigned long u = get_ulong ();
  if ((u & 1) != 0) {
    return -long (u >> 1);
  } else {
    return long (u >> 1);
  }
}

inline unsigned long
OASISReader::get_ulong_for_divider ()
{
  unsigned long l = get_ulong ();
  if (l == 0) {
    error (tl::to_string (QObject::tr ("Divider must not be zero")));
  }
  return l;
}

inline unsigned long 
OASISReader::get_ulong ()
{
  unsigned long v = 0;
  unsigned long vm = 1;
  char c;
  
  do {
    unsigned char *b = (unsigned char *) m_stream.get (1);
    if (! b) {
      error (tl::to_string (QObject::tr ("Unexpected end-of-file")));
      return 0;
    }
    c = *b;
    if (vm > std::numeric_limits <unsigned long>::max () / 128 && 
        (unsigned long) (c & 0x7f) > (std::numeric_limits <unsigned long>::max () / vm)) {
      error (tl::to_string (QObject::tr ("Unsigned long value overflow")));
    }
    v += (unsigned long) (c & 0x7f) * vm;
    vm <<= 7;
  } while ((c & 0x80) != 0);

  return v;
}

inline int 
OASISReader::get_int ()
{
  unsigned int u = get_uint ();
  if ((u & 1) != 0) {
    return -int (u >> 1);
  } else {
    return int (u >> 1);
  }
}

inline unsigned int 
OASISReader::get_uint ()
{
  unsigned int v = 0;
  unsigned int vm = 1;
  char c;
  
  do {
    unsigned char *b = (unsigned char *) m_stream.get (1);
    if (! b) {
      error (tl::to_string (QObject::tr ("Unexpected end-of-file")));
      return 0;
    }
    c = *b;
    if (vm > std::numeric_limits <unsigned int>::max () / 128 && 
        (unsigned int) (c & 0x7f) > (std::numeric_limits <unsigned int>::max () / vm)) {
      error (tl::to_string (QObject::tr ("Unsigned integer value overflow")));
    }
    v += (unsigned int) (c & 0x7f) * vm;
    vm <<= 7;
  } while ((c & 0x80) != 0);

  return v;
}

std::string 
OASISReader::get_str ()
{
  std::string s;
  get_str (s);
  return s;
}

void
OASISReader::get_str (std::string &s)
{
  size_t l = 0;
  get (l);

  char *b = (char *) m_stream.get (l);
  if (b) {
    s.assign (b, l);
  } else {
    s = std::string ();
  }
}

double
OASISReader::get_real ()
{
  unsigned int t = get_uint ();

  if (t == 0) {

    return double (get_ulong ()); 

  } else if (t == 1) {

    return -double (get_ulong ()); 

  } else if (t == 2) {

    return 1.0 / double (get_ulong_for_divider ());

  } else if (t == 3) {

    return -1.0 / double (get_ulong_for_divider ());

  } else if (t == 4) {

    double d = double (get_ulong ());
    return d / double (get_ulong_for_divider ());

  } else if (t == 5) {

    double d = double (get_ulong ());
    return -d / double (get_ulong_for_divider ());

  } else if (t == 6) {

    union {
      float f;
      uint32_t i;
    } i2f;

    unsigned char *b = (unsigned char *) m_stream.get (sizeof (i2f.i));
    if (! b) {
      error (tl::to_string (QObject::tr ("Unexpected end-of-file")));
    }
    i2f.i = 0;
    b += sizeof (i2f.i);
    for (unsigned int i = 0; i < sizeof (i2f.i); ++i) {
      i2f.i = (i2f.i << 8) + uint32_t (*--b);
    }

    return double (i2f.f);

  } else if (t == 7) {

    union {
      double d;
      uint64_t i;
    } i2f;

    unsigned char *b = (unsigned char *) m_stream.get (sizeof (i2f.i));
    if (! b) {
      error (tl::to_string (QObject::tr ("Unexpected end-of-file")));
    }
    i2f.i = 0;
    b += sizeof (i2f.i);
    for (unsigned int i = 0; i < sizeof (i2f.i); ++i) {
      i2f.i = (i2f.i << 8) + uint64_t (*--b);
    }

    return double (i2f.d);

  } else {
    error (tl::sprintf (tl::to_string (QObject::tr ("Invalid real type %d")), t));
    return 0.0;
  }
}

db::Coord
OASISReader::get_ucoord (unsigned long grid)
{
  unsigned long long lx = 0;
  get (lx);
  lx *= grid;
  if (lx > (unsigned long long) (std::numeric_limits <db::Coord>::max ())) {
    error (tl::to_string (QObject::tr ("Coordinate value overflow")));
  }
  return db::Coord (lx);
}

OASISReader::distance_type
OASISReader::get_ucoord_as_distance (unsigned long grid)
{
  unsigned long long lx = 0;
  get (lx);
  lx *= grid;
  if (lx > (unsigned long long) (std::numeric_limits <distance_type>::max ())) {
    error (tl::to_string (QObject::tr ("Coordinate value overflow")));
  }
  return distance_type (lx);
}

db::Coord
OASISReader::get_coord (long grid)
{
  long long lx = 0;
  get (lx);
  lx *= grid;
  if (lx < (long long) (std::numeric_limits <db::Coord>::min ()) ||
      lx > (long long) (std::numeric_limits <db::Coord>::max ())) {
    error (tl::to_string (QObject::tr ("Coordinate value overflow")));
  }
  return db::Coord (lx);
}

db::Vector
OASISReader::get_2delta (long grid)
{
  unsigned long long l1 = 0;
  get (l1);

  long long lx = l1 >> 2;
  lx *= grid;
  if (lx > (long long) (std::numeric_limits <db::Coord>::max ())) {
    error (tl::to_string (QObject::tr ("Coordinate value overflow")));
  }
  db::Coord x = lx;

  switch (l1 & 3) {
  case 0:
    return db::Vector (x, 0);
  case 1:
    return db::Vector (0, x);
  case 2:
    return db::Vector (-x, 0);
  case 3:
  default:
    return db::Vector (0, -x);
  }
}

db::Vector
OASISReader::get_3delta (long grid)
{
  unsigned long long l1 = 0;
  get (l1);

  long long lx = l1 >> 3;
  lx *= grid;
  if (lx > (long long) (std::numeric_limits <db::Coord>::max ())) {
    error (tl::to_string (QObject::tr ("Coordinate value overflow")));
  }
  db::Coord x = lx;

  switch (l1 & 7) {
  case 0:
    return db::Vector (x, 0);
  case 1:
    return db::Vector (0, x);
  case 2:
    return db::Vector (-x, 0);
  case 3:
    return db::Vector (0, -x);
  case 4:
    return db::Vector (x, x);
  case 5:
    return db::Vector (-x, x);
  case 6:
    return db::Vector (-x, -x);
  case 7:
  default:
    return db::Vector (x, -x);
  }
}

db::Vector
OASISReader::get_gdelta (long grid)
{
  unsigned long long l1 = 0;
  get (l1);

  if ((l1 & 1) != 0) {

    long long lx = ((l1 & 2) == 0 ? (long long) (l1 >> 2) : -(long long) (l1 >> 2));
    lx *= grid;
    if (lx < (long long) (std::numeric_limits <db::Coord>::min ()) ||
        lx > (long long) (std::numeric_limits <db::Coord>::max ())) {
      error (tl::to_string (QObject::tr ("Coordinate value overflow")));
    }

    long long ly;
    get (ly);
    ly *= grid;
    if (ly < (long long) (std::numeric_limits <db::Coord>::min ()) ||
        ly > (long long) (std::numeric_limits <db::Coord>::max ())) {
      error (tl::to_string (QObject::tr ("Coordinate value overflow")));
    }
    
    return db::Vector (db::Coord (lx), db::Coord (ly));

  } else {

    long long lx = l1 >> 4;
    lx *= grid;
    if (lx > (long long) (std::numeric_limits <db::Coord>::max ())) {
      error (tl::to_string (QObject::tr ("Coordinate value overflow")));
    }
    db::Coord x = lx;

    switch ((l1 >> 1) & 7) {
    case 0:
      return db::Vector (x, 0);
    case 1:
      return db::Vector (0, x);
    case 2:
      return db::Vector (-x, 0);
    case 3:
      return db::Vector (0, -x);
    case 4:
      return db::Vector (x, x);
    case 5:
      return db::Vector (-x, x);
    case 6:
      return db::Vector (-x, -x);
    case 7:
    default:
      return db::Vector (x, -x);
    }

  }
}

void 
OASISReader::error (const std::string &msg)
{
  throw OASISReaderException (msg, m_stream.pos (), m_cellname.c_str ());
}

void 
OASISReader::warn (const std::string &msg) 
{
  if (warnings_as_errors ()) {
    error (msg);
  } else {
    // TODO: compress
    tl::warn << msg 
             << tl::to_string (QObject::tr (" (position=")) << m_stream.pos ()
             << tl::to_string (QObject::tr (", cell=")) << m_cellname
             << ")";
  }
}

std::pair <bool, unsigned int> 
OASISReader::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) {

    return ll;

  } else {

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

    //  resolve OASIS name if possible
    const tl::interval_map <db::ld_type, std::string> *names_dmap = m_layernames.mapped (dl.layer);
    if (names_dmap != 0) {
      const std::string *name = names_dmap->mapped (dl.datatype);
      if (name != 0) {
        lp.name = *name;
      }
    }
      
    unsigned int ll = layout.insert_layer (lp);
    m_layer_map.map (dl, ll, lp);

    m_layers_created.insert (ll);

    return std::make_pair (true, ll);

  }
}

/**
 *  @brief A helper class to join two datatype layer name map members
 */
struct LNameJoinOp1
{
  void operator() (std::string &a, const std::string &b)
  {
    if (a != b) {
      if (! a.empty ()) {
        a += ";";
      } 
      a += b;
    }
  }
};

/**
 *  @brief A helper class to join two layer map members 
 *  This implementation basically merged the datatype maps.
 */
struct LNameJoinOp2
{
  void operator() (tl::interval_map<db::ld_type, std::string> &a, const tl::interval_map<db::ld_type, std::string> &b)
  {
    LNameJoinOp1 op1;
    a.add (b.begin (), b.end (), op1);
  }
};

/**
 *  @brief Marks the beginning of a new table
 *
 *  This method will update m_table_start which is the location used as
 *  the start position of a strict mode table. Every record except CBLOCK
 *  will update this position to point after the record. Hence m_table_start 
 *  points to the beginning of a table when PROPNAME, CELLNAME or any 
 *  other table-contained record is encountered.
 *  Since CBLOCK does not update this record, the position of the table will
 *  be the location of CBLOCK rather than that of the name record itself.
 *  PAD records will also call this method, so the beginning of a table 
 *  is right after any preceeding PAD records and exactly at the location
 *  of the first name record after PADs.
 */
void 
OASISReader::mark_start_table ()
{
  //  we need to this this to really finish a CBLOCK - this is a flaw
  //  in the inflating reader, but it's hard to fix.
  get_byte ();
  m_stream.unget (1);

  //  now we can fetch the position
  m_table_start = m_stream.pos ();
}

void 
OASISReader::read_offset_table ()
{
  unsigned long of = 0;

  of = get_uint ();
  m_table_cellname = get_ulong ();
  if (m_table_cellname != 0 && m_expect_strict_mode >= 0 && ((of == 0) != (m_expect_strict_mode == 0))) {
    warn (tl::to_string (QObject::tr ("CELLNAME offset table has unexpected strict mode")));
  }

  of = get_uint ();
  m_table_textstring = get_ulong ();
  if (m_table_textstring != 0 && m_expect_strict_mode >= 0 && ((of == 0) != (m_expect_strict_mode == 0))) {
    warn (tl::to_string (QObject::tr ("TEXTSTRING offset table has unexpected strict mode")));
  }

  of = get_uint ();
  m_table_propname = get_ulong ();
  if (m_table_propname != 0 && m_expect_strict_mode >= 0 && ((of == 0) != (m_expect_strict_mode == 0))) {
    warn (tl::to_string (QObject::tr ("PROPNAME offset table has unexpected strict mode")));
  }

  of = get_uint ();
  m_table_propstring = get_ulong ();
  if (m_table_propstring != 0 && m_expect_strict_mode >= 0 && ((of == 0) != (m_expect_strict_mode == 0))) {
    warn (tl::to_string (QObject::tr ("PROPSTRING offset table has unexpected strict mode")));
  }

  of = get_uint ();
  m_table_layername = get_ulong ();
  if (m_table_layername != 0 && m_expect_strict_mode >= 0 && ((of == 0) != (m_expect_strict_mode == 0))) {
    warn (tl::to_string (QObject::tr ("LAYERNAME offset table has unexpected strict mode")));
  }

  //  XNAME table ignored currently
  get_uint ();
  get_ulong ();
}

static const char magic_bytes[] = { "%SEMI-OASIS\015\012" };

void 
OASISReader::do_read (db::Layout &layout)
{
  tl::SelfTimer timer (tl::verbosity () >= 21, "File read");

  unsigned char r;
  char *mb;

  //  prepare
  m_s_gds_property_name_id = layout.properties_repository ().prop_name_id ("S_GDS_PROPERTY");
  m_klayout_context_property_name_id = layout.properties_repository ().prop_name_id ("KLAYOUT_CONTEXT");

  //  read magic bytes
  mb = (char *) m_stream.get (sizeof (magic_bytes) - 1);
  if (! mb) {
    error (tl::to_string (QObject::tr ("File too short")));
    return;
  }
  if (strncmp (mb, magic_bytes, sizeof (magic_bytes) - 1) != 0) {
    error (tl::to_string (QObject::tr ("Format error (missing magic bytes)")));
  }

  //  read first record
  r = get_byte ();
  if (r != 1 /*START*/) {
    error (tl::to_string (QObject::tr ("Format error (START record expected)")));
  }

  std::string v = get_str ();
  if (v != "1.0") {
    error (tl::sprintf (tl::to_string (QObject::tr ("Format error (only version 1.0 is supported, file has version %s)")), v));
  }

  double res = get_real ();
  if (res < 1e-6) {
    error (tl::sprintf (tl::to_string (QObject::tr ("Invalid resolution of %g")), res));
  }

  //  compute database unit in pixel per meter
  m_dbu = 1.0e-6 / res;
  layout.dbu (m_dbu * 1e6);

  //  read over table offsets if required
  bool table_offsets_at_end = get_uint ();
  if (! table_offsets_at_end) {
    read_offset_table ();
  }

  //  reset the strict mode checking locations
  m_first_cellname = 0;
  m_first_propname = 0;
  m_first_propstring = 0;
  m_first_textstring = 0;
  m_first_layername = 0;
  m_in_table = NotInTable;
  m_table_cellname = 0;
  m_table_propname = 0;
  m_table_propstring = 0;
  m_table_textstring = 0;
  m_table_layername = 0;

  //  define the name id counters
  unsigned long cellname_id = 0;
  unsigned long textstring_id = 0;
  unsigned long propstring_id = 0;
  unsigned long propname_id = 0;

  //  id mode (explicit or implicit)
  enum id_mode { any, expl, impl };
  id_mode cellname_id_mode = any;
  id_mode textstring_id_mode = any;
  id_mode propstring_id_mode = any;
  id_mode propname_id_mode = any;

  m_cellnames.clear ();
  m_cellname_properties.clear ();
  m_textstrings.clear ();
  m_propstrings.clear ();
  m_propnames.clear ();
  m_layernames.clear ();
  m_cells_by_id.clear ();
  m_cells_by_name.clear ();
  m_defined_cells_by_id.clear ();
  m_defined_cells_by_name.clear ();
  m_mapped_cellnames.clear ();

  m_instances.clear ();
  m_instances_with_props.clear ();

  db::PropertiesRepository::properties_set layout_properties;

  mark_start_table ();

  //  read next record
  while (true) {

    r = get_byte ();

    if (r == 0 /*PAD*/) {

      //  simply skip.
      mark_start_table ();

    } else if (r == 2 /*END*/) {

      //  done
      break;

    } else if (r == 3 || r == 4 /*CELLNAME*/) {

      if (m_first_cellname == 0) {
        m_first_cellname = m_table_start;
      } else if (m_expect_strict_mode == 1 && m_in_table != InCELLNAME && m_first_cellname != 0) {
        warn (tl::to_string (QObject::tr ("CELLNAME outside table in strict mode")));
      }
      m_in_table = InCELLNAME;

      //  there cannot be more file level properties .. store what we have
      if (! layout_properties.empty ()) {
        layout.prop_id (layout.properties_repository ().properties_id (layout_properties));
        layout_properties.clear ();
      }

      //  read a cell name
      std::string name = get_str ();

      //  and the associated id
      unsigned long id = cellname_id;
      if (r == 3) {
        if (cellname_id_mode == expl) {
          error (tl::to_string (QObject::tr ("Explicit and implicit CELLNAME modes cannot be mixed")));
        }
        cellname_id_mode = impl;
        ++cellname_id;
      } else {
        if (cellname_id_mode == impl) {
          error (tl::to_string (QObject::tr ("Explicit and implicit CELLNAME modes cannot be mixed")));
        }
        cellname_id_mode = expl;
        get (id);
      }

      if (! m_cellnames.insert (std::make_pair (id, name)).second) {
        error (tl::sprintf (tl::to_string (QObject::tr ("A CELLNAME with id %ld is present already")), id));
      }

      reset_modal_variables ();

      std::pair<bool, db::properties_id_type> pp = read_element_properties (layout.properties_repository (), true);
      if (pp.first) {
        m_cellname_properties.insert (std::make_pair (id, pp.second));
      }

    } else if (r == 5 || r == 6 /*TEXTSTRING*/) {

      if (m_first_textstring == 0) {
        m_first_textstring = m_table_start;
      } else if (m_expect_strict_mode == 1 && m_in_table != InTEXTSTRING && m_first_textstring != 0) {
        warn (tl::to_string (QObject::tr ("TEXTSTRING outside table in strict mode")));
      }
      m_in_table = InTEXTSTRING;


      //  there cannot be more file level properties .. store what we have
      if (! layout_properties.empty ()) {
        layout.prop_id (layout.properties_repository ().properties_id (layout_properties));
        layout_properties.clear ();
      }

      //  read a text string
      std::string name = get_str ();

      //  and the associated id
      unsigned long id = textstring_id;
      if (r == 5) {
        if (textstring_id_mode == expl) {
          error (tl::to_string (QObject::tr ("Explicit and implicit TEXTSTRING modes cannot be mixed")));
        }
        textstring_id_mode = impl;
        ++textstring_id;
      } else {
        if (textstring_id_mode == impl) {
          error (tl::to_string (QObject::tr ("Explicit and implicit TEXTSTRING modes cannot be mixed")));
        }
        textstring_id_mode = expl;
        get (id);
      }

      if (! m_textstrings.insert (std::make_pair (id, name)).second) {
        error (tl::sprintf (tl::to_string (QObject::tr ("A TEXTSTRING with id %ld is present already")), id));
      }

      reset_modal_variables ();

      //  ignore properties attached to this name item
      read_element_properties (layout.properties_repository (), true);

    } else if (r == 7 || r == 8 /*PROPNAME*/) {

      if (m_first_propname == 0) {
        m_first_propname = m_table_start;
      } else if (m_expect_strict_mode == 1 && m_in_table != InPROPNAME && m_first_propname != 0) {
        warn (tl::to_string (QObject::tr ("PROPNAME outside table in strict mode")));
      }
      m_in_table = InPROPNAME;

      //  there cannot be more file level properties .. store what we have
      if (! layout_properties.empty ()) {
        layout.prop_id (layout.properties_repository ().properties_id (layout_properties));
        layout_properties.clear ();
      }

      //  read a property name
      std::string name = get_str ();

      //  and the associated id
      unsigned long id = propname_id;
      if (r == 7) {
        if (propname_id_mode == expl) {
          error (tl::to_string (QObject::tr ("Explicit and implicit PROPNAME modes cannot be mixed")));
        }
        propname_id_mode = impl;
        ++propname_id;
      } else {
        if (propname_id_mode == impl) {
          error (tl::to_string (QObject::tr ("Explicit and implicit PROPNAME modes cannot be mixed")));
        }
        propname_id_mode = expl;
        get (id);
      }

      if (! m_propnames.insert (std::make_pair (id, name)).second) {
        error (tl::sprintf (tl::to_string (QObject::tr ("A PROPNAME with id %ld is present already")), id));
      }

      //  resolve forward references to property names
      std::map <unsigned long, db::property_names_id_type>::iterator pf = m_propname_forward_references.find (id);
      if (pf != m_propname_forward_references.end ()) {

        if (name == "S_GDS_PROPERTY") {

          db::PropertiesRepository &rep = layout.properties_repository ();
          db::property_names_id_type s_gds_name_id = pf->second;

          //  exchange the properties in the repository: first locate all
          //  property sets that are affected
          std::vector <db::properties_id_type> pids;
          for (db::PropertiesRepository::iterator p = rep.begin (); p != rep.end (); ++p) {
            if (p->second.find (s_gds_name_id) != p->second.end ()) {
              pids.push_back (p->first);
            }
          }

          //  create new property sets for the ones we found
          for (std::vector <db::properties_id_type>::const_iterator pid = pids.begin (); pid != pids.end (); ++pid) {

            const db::PropertiesRepository::properties_set &old_set = rep.properties (*pid);
            db::PropertiesRepository::properties_set new_set;

            for (db::PropertiesRepository::properties_set::const_iterator s = old_set.begin (); s != old_set.end (); ++s) {
              if (s->first == s_gds_name_id) {

                if (!s->second.is_list () || s->second.get_list ().size () != 2) {
                  error (tl::to_string (QObject::tr ("S_GDS_PROPERTY must have a value list with exactly two elements")));
                }

                new_set.insert (std::make_pair (rep.prop_name_id (s->second.get_list () [0]), s->second.get_list () [1]));

              } else {
                new_set.insert (*s);
              }
            }

            rep.change_properties (*pid, new_set);

          }

        }

        layout.properties_repository ().change_name (pf->second, tl::Variant (name));
        m_propname_forward_references.erase (pf);

      }

      reset_modal_variables ();

      //  ignore properties attached to this name item
      read_element_properties (layout.properties_repository (), true);

    } else if (r == 9 || r == 10 /*PROPSTRING*/) {

      if (m_first_propstring == 0) {
        m_first_propstring = m_table_start;
      } else if (m_expect_strict_mode == 1 && m_in_table != InPROPSTRING && m_first_propstring != 0) {
        warn (tl::to_string (QObject::tr ("PROPSTRING outside table in strict mode")));
      }
      m_in_table = InPROPSTRING;

      //  there cannot be more file level properties .. store what we have
      if (! layout_properties.empty ()) {
        layout.prop_id (layout.properties_repository ().properties_id (layout_properties));
        layout_properties.clear ();
      }

      //  read a property string
      std::string name = get_str ();

      //  and the associated id
      unsigned long id = propstring_id;
      if (r == 9) {
        if (propstring_id_mode == expl) {
          error (tl::to_string (QObject::tr ("Explicit and implicit PROPSTRING modes cannot be mixed")));
        }
        propstring_id_mode = impl;
        ++propstring_id;
      } else {
        if (propstring_id_mode == impl) {
          error (tl::to_string (QObject::tr ("Explicit and implicit PROPSTRING modes cannot be mixed")));
        }
        propstring_id_mode = expl;
        get (id);
      }

      if (! m_propstrings.insert (std::make_pair (id, name)).second) {
        error (tl::sprintf (tl::to_string (QObject::tr ("A PROPSTRING with id %ld is present already")), id));
      }

      std::map<unsigned long, std::string>::iterator fw = m_propvalue_forward_references.find (id);
      if (fw != m_propvalue_forward_references.end ()) {
        fw->second = name;
      }

      reset_modal_variables ();

      //  ignore properties attached to this name item
      read_element_properties (layout.properties_repository (), true);

    } else if (r == 11 || r == 12 /*LAYERNAME*/) {

      if (m_first_layername == 0) {
        m_first_layername = m_table_start;
      } else if (m_expect_strict_mode == 1 && m_in_table != InLAYERNAME && m_first_layername != 0) {
        warn (tl::to_string (QObject::tr ("LAYERNAME outside table in strict mode")));
      }
      m_in_table = InLAYERNAME;

      //  there cannot be more file level properties .. store what we have
      if (! layout_properties.empty ()) {
        layout.prop_id (layout.properties_repository ().properties_id (layout_properties));
        layout_properties.clear ();
      }

      //  read a layer name 
      std::string name = get_str ();

      db::ld_type dt1 = 0, dt2 = std::numeric_limits<db::ld_type>::max () - 1;
      db::ld_type l1 = 0, l2 = std::numeric_limits<db::ld_type>::max () - 1;
      unsigned int it;

      it = get_uint ();
      if (it == 0) {
        //  keep limits
      } else if (it == 1) {
        l2 = get_uint ();
      } else if (it == 2) {
        l1 = get_uint ();
      } else if (it == 3) {
        l1 = get_uint ();
        l2 = l1;
      } else if (it == 4) {
        l1 = get_uint ();
        l2 = get_uint ();
      } else {
        error (tl::to_string (QObject::tr ("Invalid LAYERNAME interval mode (layer)")));
      }

      it = get_uint ();
      if (it == 0) {
        //  keep limits
      } else if (it == 1) {
        dt2 = get_uint ();
      } else if (it == 2) {
        dt1 = get_uint ();
      } else if (it == 3) {
        dt1 = get_uint ();
        dt2 = dt1;
      } else if (it == 4) {
        dt1 = get_uint ();
        dt2 = get_uint ();
      } else {
        error (tl::to_string (QObject::tr ("Invalid LAYERNAME interval mode (datatype)")));
      }

      //  add to the layer name map
      tl::interval_map <db::ld_type, std::string> dt_map;
      LNameJoinOp1 op1;
      dt_map.add (dt1, dt2 + 1, name, op1);
      LNameJoinOp2 op2;
      m_layernames.add (l1, l2 + 1, dt_map, op2);

      //  rename layers created before if required
      for (std::set<unsigned int>::const_iterator i = m_layers_created.begin (); i != m_layers_created.end (); ++i) {
        const db::LayerProperties &lp = layout.get_properties (*i);
        if (lp.layer >= l1 && lp.layer <= l2 && lp.datatype >= dt1 && lp.datatype <= dt2 && lp.name != name) {
          //  need to rename: add a new madding to m_layer_map and adjust the layout's layer properties
          db::LayerProperties lpp = lp;
          LNameJoinOp1 nj;
          nj (lpp.name, name);
          layout.set_properties (*i, lpp);
          m_layer_map.map (LDPair (lp.layer, lp.datatype), *i, lpp);
        }
      }
      
      reset_modal_variables ();

      //  ignore properties attached to this name item
      read_element_properties (layout.properties_repository (), true);

    } else if (r == 28 || r == 29 /*PROPERTY*/) {

      //  unrecognized property: store in layout properties
      if (r == 28) {
        read_properties (layout.properties_repository ());
      }

      store_last_properties (layout.properties_repository (), layout_properties, true);

      mark_start_table ();

    } else if (r == 30 || r == 31 /*XNAME*/) {

      //  there cannot be more file level properties .. store what we have
      if (! layout_properties.empty ()) {
        layout.prop_id (layout.properties_repository ().properties_id (layout_properties));
        layout_properties.clear ();
      }

      //  read a XNAME: it is simply ignored
      get_ulong ();
      get_str ();
      if (r == 31) {
        get_ulong ();
      }

      reset_modal_variables ();

      //  ignore properties attached to this name item
      read_element_properties (layout.properties_repository (), true);

    } else if (r == 13 || r == 14 /*CELL*/) {

      m_in_table = NotInTable;

      //  there cannot be more file level properties .. store what we have
      if (! layout_properties.empty ()) {
        layout.prop_id (layout.properties_repository ().properties_id (layout_properties));
        layout_properties.clear ();
      }

      db::cell_index_type cell_index = 0;

      //  read a cell
      if (r == 13) {

        unsigned long id = 0;
        get (id);
        if (! m_defined_cells_by_id.insert (id).second) {
          error (tl::sprintf (tl::to_string (QObject::tr ("A cell with id %ld is defined already")), id));
        }

        std::map <unsigned long, db::cell_index_type>::const_iterator c = m_cells_by_id.find (id);
        if (c != m_cells_by_id.end ()) {

          cell_index = c->second;
          layout.cell (cell_index).set_ghost_cell (false);

        } else {

          std::map <unsigned long, std::string>::const_iterator name = m_cellnames.find (id);
          if (name == m_cellnames.end ()) {

            cell_index = layout.add_cell ();
            //  force a cell rename to empty to avoid name clashes of the generated
            //  $x names with the same inside the OASIS file.
            layout.rename_cell (cell_index, "");
            m_forward_references.insert (std::make_pair (id, cell_index));

          } else {

            cell_index = make_cell (layout, name->second.c_str (), false);
            m_cells_by_name.insert (std::make_pair (name->second, cell_index));

          }

          m_cells_by_id.insert (std::make_pair (id, cell_index));

        }

      } else {

        if (m_expect_strict_mode == 1) {
          warn (tl::to_string (QObject::tr ("CELL names must be references to CELLNAME ids in strict mode")));
        }

        std::string name = get_str ();
        if (! m_defined_cells_by_name.insert (name).second) {
          error (tl::sprintf (tl::to_string (QObject::tr ("A cell with name %s is defined already")), name.c_str ()));
        }

        std::map <std::string, db::cell_index_type>::const_iterator c = m_cells_by_name.find (name);
        if (c != m_cells_by_name.end ()) {

          cell_index = c->second;
          layout.cell (cell_index).set_ghost_cell (false);

        } else {

          cell_index = make_cell (layout, name.c_str (), false);
          m_cells_by_name.insert (std::make_pair (name, cell_index));

        }

      }

      reset_modal_variables ();
      mark_start_table ();

      do_read_cell (cell_index, layout);

    } else if (r == 34 /*CBLOCK*/) {

      unsigned int type = get_uint ();
      if (type != 0) {
        error (tl::sprintf (tl::to_string (QObject::tr ("Invalid CBLOCK compression type %d")), type));
      }

      get_uint ();  // uncomp-byte-count - not needed
      get_uint ();  // comp-byte-count - not needed

      //  put the stream into deflating mode
      m_stream.inflate ();

    } else {
      error (tl::sprintf (tl::to_string (QObject::tr ("Invalid record type on global level %d")), int (r)));
    }

  }

  if (! layout_properties.empty ()) {
    layout.prop_id (layout.properties_repository ().properties_id (layout_properties));
    layout_properties.clear ();
  }

  size_t pt = m_stream.pos ();

  if (table_offsets_at_end) {
    read_offset_table ();
  }

  //  read over tail and discard
  mb = (char *) m_stream.get (pt + 254 - m_stream.pos ());
  if (! mb) {
    error (tl::to_string (QObject::tr ("Format error (too few bytes after END record)")));
  }

  //  check if there are no more bytes
  mb = (char *) m_stream.get (254);
  if (mb) {
    error (tl::to_string (QObject::tr ("Format error (too many bytes after END record)")));
  }

  for (std::map <unsigned long, const db::StringRef *>::const_iterator fw = m_text_forward_references.begin (); fw != m_text_forward_references.end (); ++fw) {
    std::map <unsigned long, std::string>::const_iterator ts = m_textstrings.find (fw->first);
    if (ts == m_textstrings.end ()) {
      error (tl::sprintf (tl::to_string (QObject::tr ("No text string defined for text string id %ld")), fw->first));
    } else {
      layout.string_repository ().change_string_ref (fw->second, ts->second);
    }
  }

  //  all forward references to property names must be resolved
  for (std::map <unsigned long, db::property_names_id_type>::const_iterator fw = m_propname_forward_references.begin (); fw != m_propname_forward_references.end (); ++fw) {
    error (tl::sprintf (tl::to_string (QObject::tr ("No property name defined for property name id %ld")), fw->first));
  }

  //  resolve all propvalue forward referenced
  if (! m_propvalue_forward_references.empty ()) {

    for (db::PropertiesRepository::non_const_iterator pi = layout.properties_repository ().begin_non_const (); pi != layout.properties_repository ().end_non_const (); ++pi) {

      for (db::PropertiesRepository::properties_set::iterator ps = pi->second.begin (); ps != pi->second.end (); ++ps) {

        if (ps->second.is_id ()) {

          unsigned long id = (unsigned long) ps->second.to_id ();
          std::map <unsigned long, std::string>::const_iterator fw = m_propvalue_forward_references.find (id);
          if (fw != m_propvalue_forward_references.end ()) {
            ps->second = tl::Variant (fw->second);
          } else {
            error (tl::sprintf (tl::to_string (QObject::tr ("No property value defined for property value id %ld")), id));
          }

        } else if (ps->second.is_list ()) {

          //  Replace list elements as well
          //  TODO: Q: can there be a list of lists? would need recursive replacement -> make that a method of tl::Variant

          const std::vector<tl::Variant> &l = ps->second.get_list ();
          bool needs_replacement = false;
          for (std::vector<tl::Variant>::const_iterator ll = l.begin (); ll != l.end () && ! needs_replacement; ++ll) {
            needs_replacement = ll->is_id ();
          }

          if (needs_replacement) {

            std::vector<tl::Variant> new_list (l);
            for (std::vector<tl::Variant>::iterator ll = new_list.begin (); ll != new_list.end (); ++ll) {
              if (ll->is_id ()) {
                unsigned long id = (unsigned long) ll->to_id ();
                std::map <unsigned long, std::string>::const_iterator fw = m_propvalue_forward_references.find (id);
                if (fw != m_propvalue_forward_references.end ()) {
                  *ll = tl::Variant (fw->second);
                } else {
                  error (tl::sprintf (tl::to_string (QObject::tr ("No property value defined for property value id %ld")), id));
                }
              }
            }

            ps->second = tl::Variant (new_list.begin (), new_list.end ());

          }

        }

      }

    }

    m_propvalue_forward_references.clear ();

  }

  for (std::map <unsigned long, db::cell_index_type>::const_iterator fw = m_forward_references.begin (); fw != m_forward_references.end (); ++fw) {

    std::map <unsigned long, std::string>::const_iterator cn = m_cellnames.find (fw->first);
    if (cn == m_cellnames.end ()) {

      error (tl::sprintf (tl::to_string (QObject::tr ("No cellname defined for cell name id %ld")), fw->first));

    } else {

      std::pair<bool, db::cell_index_type> c = layout.cell_by_name (cn->second.c_str ()); 
      if (c.first) {

        //  needed, since we have disabled updates
        layout.force_update ();

        //  add-on reading of forward-referenced cell: need to copy the new cell to the original one plus 
        //  change instances of the new cell and delete the new cell then.

        const db::Cell &new_cell = layout.cell (fw->second);
        db::Cell &org_cell = layout.cell (c.second);

        //  copy over the instances
        for (db::Cell::const_iterator i = new_cell.begin (); ! i.at_end (); ++i) {
          org_cell.insert (*i);
        }

        //  copy over the shapes
        for (unsigned int l = 0; l < layout.layers (); ++l) {
          if (layout.is_valid_layer (l) && ! new_cell.shapes (l).empty ()) {
            org_cell.shapes (l).insert (new_cell.shapes (l));
          }
        }

        //  replace all instances of the new cell with the original one
        std::vector<std::pair<db::cell_index_type, db::Instance> > parents;
        for (db::Cell::parent_inst_iterator pi = new_cell.begin_parent_insts (); ! pi.at_end (); ++pi) {
          parents.push_back (std::make_pair (pi->parent_cell_index (), pi->child_inst ()));
        }

        for (std::vector<std::pair<db::cell_index_type, db::Instance> >::const_iterator p = parents.begin (); p != parents.end (); ++p) {
          db::CellInstArray ia = p->second.cell_inst ();
          ia.object ().cell_index (org_cell.cell_index ());
          layout.cell (p->first).replace (p->second, ia);
        }

        //  finally delete the new cell
        layout.delete_cell (new_cell.cell_index ());

      } else {
        layout.rename_cell (fw->second, cn->second.c_str ());
      }

    }

  }

  //  attach the properties found in CELLNAME to the cells (which may have other properties)
  for (std::map<unsigned long, db::properties_id_type>::const_iterator p = m_cellname_properties.begin (); p != m_cellname_properties.end (); ++p) {

    std::map <unsigned long, db::cell_index_type>::const_iterator c = m_cells_by_id.find (p->first);
    if (c != m_cells_by_id.end ()) {

      db::PropertiesRepository::properties_set cnp = layout.properties_repository ().properties (p->second);

      //  Merge existing properties with the ones from CELLNAME
      db::Cell &cell = layout.cell (c->second);
      if (cell.prop_id () != 0) {
        db::PropertiesRepository::properties_set cp = layout.properties_repository ().properties (cell.prop_id ());
        cnp.insert (cp.begin (), cp.end ());
      }

      cell.prop_id (layout.properties_repository ().properties_id (cnp));

    }

  }

  //  Check the table offsets vs. real occurance
  if (m_first_cellname != 0 && m_first_cellname != m_table_cellname && m_expect_strict_mode == 1) {
    warn (tl::to_string (QObject::tr ("CELLNAME table offset does not match first occurance of CELLNAME in strict mode - %1 vs. %2").arg (m_table_cellname).arg (m_first_cellname)));
  }
  if (m_first_propname != 0 && m_first_propname != m_table_propname && m_expect_strict_mode == 1) {
    warn (tl::to_string (QObject::tr ("PROPNAME table offset does not match first occurance of PROPNAME in strict mode - %1 vs. %2").arg (m_table_propname).arg (m_first_propname)));
  }
  if (m_first_propstring != 0 && m_first_propstring != m_table_propstring && m_expect_strict_mode == 1) {
    warn (tl::to_string (QObject::tr ("PROPSTRING table offset does not match first occurance of PROPSTRING in strict mode - %1 vs. %2").arg (m_table_propstring).arg (m_first_propstring)));
  }
  if (m_first_layername != 0 && m_first_layername != m_table_layername && m_expect_strict_mode == 1) {
    warn (tl::to_string (QObject::tr ("LAYERNAME table offset does not match first occurance of LAYERNAME in strict mode - %1 vs. %2").arg (m_table_layername).arg (m_first_layername)));
  }
  if (m_first_textstring != 0 && m_first_textstring != m_table_textstring && m_expect_strict_mode == 1) {
    warn (tl::to_string (QObject::tr ("TEXTSTRING table offset does not match first occurance of TEXTSTRING in strict mode - %1 vs. %2").arg (m_table_textstring).arg (m_first_textstring)));
  }
}

void
OASISReader::store_last_properties (db::PropertiesRepository &rep, db::PropertiesRepository::properties_set &properties, bool ignore_special)
{
  if (! m_read_properties) {

    //  All properties are ignored

  } else if (mm_last_property_is_sprop.get () && mm_last_property_name.get () == m_s_gds_property_name_id) {

    if (mm_last_value_list.get ().size () != 2) {
      error (tl::to_string (QObject::tr ("S_GDS_PROPERTY must have a value list with exactly two elements")));
    }

    properties.insert (std::make_pair (rep.prop_name_id (mm_last_value_list.get () [0]), mm_last_value_list.get () [1]));

  } else if (ignore_special && ! m_read_all_properties && mm_last_property_is_sprop.get ()) {

    //  Special properties are not turned into user properties except S_GDS_PROPERTY.
    //  This is mode is used for cells and layouts so the standard properties do not appear as user properties.
    //  For shapes we need to keep the special ones since they may be forward-references S_GDS_PROPERTY names.
    
  } else if (mm_last_value_list.get ().size () == 0) {
    properties.insert (std::make_pair (mm_last_property_name.get (), tl::Variant ()));
  } else if (mm_last_value_list.get ().size () == 1) {
    properties.insert (std::make_pair (mm_last_property_name.get (), tl::Variant (mm_last_value_list.get () [0])));
  } else if (mm_last_value_list.get ().size () > 1) {
    properties.insert (std::make_pair (mm_last_property_name.get (), tl::Variant (mm_last_value_list.get ().begin (), mm_last_value_list.get ().end ())));
  }
}

std::pair <bool, db::properties_id_type> 
OASISReader::read_element_properties (db::PropertiesRepository &rep, bool ignore_special)
{
  db::PropertiesRepository::properties_set properties;

  mark_start_table ();

  while (true) {

    unsigned char m = get_byte ();

    if (m == 0 /*PAD*/) {

      //  skip PAD.
      mark_start_table ();
     
    } else if (m == 34 /*CBLOCK*/) {

      unsigned int type = get_uint ();
      if (type != 0) {
        error (tl::sprintf (tl::to_string (QObject::tr ("Invalid CBLOCK compression type %d")), type));
      }

      get_uint ();  // uncomp-byte-count - not needed
      get_uint ();  // comp-byte-count - not needed

      //  put the stream into deflating mode
      m_stream.inflate ();

    } else if (m == 28 /*PROPERTY*/) {

      read_properties (rep);
      store_last_properties (rep, properties, ignore_special);

      mark_start_table ();

    } else if (m == 29 /*PROPERTY*/) {

      store_last_properties (rep, properties, ignore_special);

      mark_start_table ();

    } else {

      m_stream.unget (1);
      break;

    }

  } 

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

void 
OASISReader::read_properties (db::PropertiesRepository &rep)
{
  unsigned char m = get_byte ();

  if (m & 0x04) {
    if (m & 0x02) {

      unsigned long id;
      get (id);

      std::map <unsigned long, std::string>::const_iterator cid = m_propnames.find (id);
      if (cid == m_propnames.end ()) {
        mm_last_property_name = rep.prop_name_id (tl::Variant (id, true /*dummy for id type*/));
        m_propname_forward_references.insert (std::make_pair (id, mm_last_property_name.get ()));
      } else {
        mm_last_property_name = rep.prop_name_id (tl::Variant (cid->second));
      }

    } else {

      if (m_expect_strict_mode == 1) {
        warn (tl::to_string (QObject::tr ("PROPERTY names must be references to PROPNAME ids in strict mode")));
      }

      mm_last_property_name = rep.prop_name_id (tl::Variant (get_str ()));

    }
  }

  mm_last_property_is_sprop = ((m & 0x01) != 0);

  if (! (m & 0x08)) {

    unsigned long n = ((unsigned long) (m >> 4)) & 0x0f;
    if (n == 15) {
      get (n);
    }

    mm_last_value_list.get_non_const ().clear ();
    mm_last_value_list.get_non_const ().reserve (n);

    while (n > 0) {

      unsigned char t = get_byte ();
      if (t < 8) {

        m_stream.unget (1);
        double v = get_real ();
        if (m_read_properties) {
          mm_last_value_list.get_non_const ().push_back (tl::Variant (v));
        }

      } else if (t == 8) {

        unsigned long l;
        get (l);
        if (m_read_properties) {
          mm_last_value_list.get_non_const ().push_back (tl::Variant (long (l)));
        }

      } else if (t == 9) {

        long l;
        get (l);
        if (m_read_properties) {
          mm_last_value_list.get_non_const ().push_back (tl::Variant (l));
        }

      } else if (t == 10 || t == 11 || t == 12) {

        if (m_expect_strict_mode == 1) {
          warn (tl::to_string (QObject::tr ("PROPERTY strings must be references to PROPSTRING ids in strict mode")));
        }

        if (m_read_properties) {
          mm_last_value_list.get_non_const ().push_back (tl::Variant (get_str ()));
        } else {
          get_str ();
        }

      } else if (t == 13 || t == 14 || t == 15) {

        unsigned long id;
        get (id);
        if (m_read_properties) {
          std::map <unsigned long, std::string>::const_iterator sid = m_propstrings.find (id);
          if (sid == m_propstrings.end ()) {
            m_propvalue_forward_references.insert (std::make_pair (id, std::string ()));
            mm_last_value_list.get_non_const ().push_back (tl::Variant (id, true /*dummy for id type*/));
          } else {
            mm_last_value_list.get_non_const ().push_back (tl::Variant (sid->second));
          }
        }

      } else {
        error (tl::sprintf (tl::to_string (QObject::tr ("Invalid property value type %d")), int (t)));
      }

      --n;

    }

    mm_last_value_list.set_initialized ();

  }
}


      
void 
OASISReader::read_pointlist (modal_variable <std::vector <db::Point> > &pointlist, bool for_polygon)
{
  unsigned int type = get_uint ();
  
  unsigned long n = 0;
  get (n);
  if (n == 0) {
    error (tl::to_string (QObject::tr ("Invalid point list: length is zero")).c_str ());
  }

  pointlist.get_non_const ().clear ();
  if ((type == 0 || type == 1) && for_polygon) {
    //  because for polygons, the pointlist will be closed implicitly
    pointlist.get_non_const ().reserve (n + 2);
  } else {
    pointlist.get_non_const ().reserve (n + 1);
  }

  pointlist.get_non_const ().push_back (db::Point ());

  if (type == 0 || type == 1) {

    bool h = (type == 0);

    db::Point pos;
    for (unsigned long i = 0; i < n; ++i) {
      db::Coord d = get_coord ();
      if (h) {
        pos += db::Vector (d, 0);
      } else {
        pos += db::Vector (0, d);
      }
      h = ! h;
      pointlist.get_non_const ().push_back (pos);
    }

    //  synthesize the last point for polygons
    if (for_polygon) {
      if ((n % 2) != 0) {
        warn (tl::to_string (QObject::tr ("Type 0 or 1 point list with odd number of points is illegal")));
      }
      if (h) {
        pointlist.get_non_const ().push_back (db::Point (0, pos.y ()));
      } else {
        pointlist.get_non_const ().push_back (db::Point (pos.x (), 0));
      }
    }

  } else if (type == 2) {

    db::Point pos;
    for (unsigned long i = 0; i < n; ++i) {
      pos += get_2delta ();
      pointlist.get_non_const ().push_back (pos);
    }

  } else if (type == 3) {

    db::Point pos;
    for (unsigned long i = 0; i < n; ++i) {
      pos += get_3delta ();
      pointlist.get_non_const ().push_back (pos);
    }

  } else if (type == 4) {

    db::Point pos;
    for (unsigned long i = 0; i < n; ++i) {
      pos += get_gdelta ();
      pointlist.get_non_const ().push_back (pos);
    }

  } else if (type == 5) {

    db::Point pos;
    db::Vector delta;
    for (unsigned long i = 0; i < n; ++i) {
      delta += get_gdelta ();
      pos += delta;
      pointlist.get_non_const ().push_back (pos);
    }

  } else {
    error (tl::sprintf (tl::to_string (QObject::tr ("Invalid point list type %d")), type));
  }

  pointlist.set_initialized ();
}

bool
OASISReader::read_repetition ()
{
  unsigned char type = get_uint ();
  if (type == 0) {
    
    //  reuse modal variable

  } else if (type == 1) {

    unsigned long nx = 0, ny = 0;
    get (nx); 
    get (ny);

    db::Coord dx = get_ucoord ();
    db::Coord dy = get_ucoord ();

    mm_repetition = new RegularRepetition (db::Vector (dx, 0), db::Vector (0, dy), dx == 0 ? 1 : nx + 2, dy == 0 ? 1 : ny + 2);

  } else if (type == 2) {

    unsigned long nx = 0;
    get (nx); 

    db::Coord dx = get_ucoord ();

    mm_repetition = new RegularRepetition (db::Vector (dx, 0), db::Vector (0, 0), dx == 0 ? 1 : nx + 2, 1);

  } else if (type == 3) {

    unsigned long ny = 0;
    get (ny);

    db::Coord dy = get_ucoord ();

    mm_repetition = new RegularRepetition (db::Vector (0, 0), db::Vector (0, dy), 1, dy == 0 ? 1 : ny + 2);

  } else if (type == 4 || type == 5) {
    
    IrregularRepetition *rep = new IrregularRepetition ();
    mm_repetition = rep;

    unsigned long n = 0;
    get (n);

    unsigned long lgrid = 1;
    if (type == 5) {
      get (lgrid);
    }

    rep->reserve (n + 1);

    db::Coord x = 0;
    for (unsigned long i = 0; i <= n; ++i) {
      db::Coord d = get_ucoord (lgrid);
      if (d != 0) {
        x += d;
        rep->push_back (db::Vector (x, 0));
      }
    }

  } else if (type == 6 || type == 7) {
    
    IrregularRepetition *rep = new IrregularRepetition ();
    mm_repetition = rep;

    unsigned long n = 0;
    get (n);

    unsigned long lgrid = 1;
    if (type == 7) {
      get (lgrid);
    }

    rep->reserve (n + 1);

    db::Coord y = 0;
    for (unsigned long i = 0; i <= n; ++i) {
      db::Coord d = get_ucoord (lgrid);
      if (d != 0) {
        y += d;
        rep->push_back (db::Vector (0, y));
      }
    }

  } else if (type == 8) {

    unsigned long n = 0, m = 0;

    get (n); 
    get (m);
    db::Vector dn = get_gdelta ();
    db::Vector dm = get_gdelta ();

    mm_repetition = new RegularRepetition (dn, dm, dn == db::Vector () ? 1 : n + 2, dm == db::Vector () ? 1 : m + 2);

  } else if (type == 9) {

    unsigned long n = 0;
    get (n); 
    db::Vector dn = get_gdelta ();

    mm_repetition = new RegularRepetition (dn, db::Vector (0, 0), dn == db::Vector () ? 1 : n + 2, 1);

  } else if (type == 10 || type == 11) {

    IrregularRepetition *rep = new IrregularRepetition ();
    mm_repetition = rep;

    unsigned long n = 0;
    get (n);

    unsigned long grid = 1;
    if (type == 11) {
      get (grid);
    }

    rep->reserve (n + 1);

    db::Vector p;
    for (unsigned long i = 0; i <= n; ++i) {
      db::Vector d = get_gdelta (grid);
      if (d != db::Vector ()) {
        p += d;
        rep->push_back (p);
      }
    }

  } else {
    error (tl::sprintf (tl::to_string (QObject::tr ("Invalid repetition type %d")), type));
  }

  return mm_repetition.get ().size () > 1;
}

db::cell_index_type
OASISReader::make_cell (db::Layout &layout, const char *cn, bool for_instance)
{
  db::cell_index_type ci = 0;

  //  map to the real name which maybe a different one due to localization
  //  of proxy cells (they are not to be reopened)
  bool is_mapped = false;
  if (! m_mapped_cellnames.empty ()) {
    std::map<tl::string, tl::string>::const_iterator n = m_mapped_cellnames.find (cn);
    if (n != m_mapped_cellnames.end ()) {
      cn = n->second.c_str ();
      is_mapped = true;
    }
  }

  std::pair<bool, db::cell_index_type> c = layout.cell_by_name (cn);
  if (c.first && (is_mapped || ! layout.cell (c.second).is_proxy ())) {

    //  cell already there: just add instance (cell might have been created through forward reference)
    //  NOTE: we don't address "reopened" proxies as proxies are always local to a layout

    ci = c.second;

    //  mark the cell as read
    if (! for_instance) {
      layout.cell (ci).set_ghost_cell (false);
    }

  } else {

    ci = layout.add_cell (cn);

    if (for_instance) {
      //  mark this cell a "ghost cell" until it's actually read
      layout.cell (ci).set_ghost_cell (true);
    }

    if (c.first) {
      //  this cell has been given a new name: remember this name for localization
      m_mapped_cellnames.insert (std::make_pair (cn, layout.cell_name (ci)));
    }

  }

  return ci;
}

void 
OASISReader::do_read_placement (unsigned char r,
                                bool xy_absolute,
                                db::Layout &layout, 
                                tl::vector<db::CellInstArray> &instances,
                                tl::vector<db::CellInstArrayWithProperties> &instances_with_props)
{
  unsigned char m = get_byte ();

  //  locate cell
  if (m & 0x80) {

    if (m & 0x40) {

      //  cell by id
      unsigned long id;
      get (id);
      std::map <unsigned long, db::cell_index_type>::const_iterator cid = m_cells_by_id.find (id);
      if (cid == m_cells_by_id.end ()) {

        //  create the cell
        std::map <unsigned long, std::string>::const_iterator name = m_cellnames.find (id);
        if (name == m_cellnames.end ()) {

          mm_placement_cell = layout.add_cell ();
          m_forward_references.insert (std::make_pair (id, mm_placement_cell.get ()));

          //  temporarily mark as "ghost cell"
          layout.cell (mm_placement_cell.get ()).set_ghost_cell (true);

        } else {

          mm_placement_cell = make_cell (layout, name->second.c_str (), true);
          m_cells_by_name.insert (std::make_pair (name->second, mm_placement_cell.get ()));
         
        }

        m_cells_by_id.insert (std::make_pair (id, mm_placement_cell.get ()));

      } else {
        mm_placement_cell = cid->second;
      }

    } else {

      //  cell by name
      std::string name;
      get_str (name);

      std::map <std::string, db::cell_index_type>::const_iterator cid = m_cells_by_name.find (name);
      if (cid == m_cells_by_name.end ()) {

        mm_placement_cell = make_cell (layout, name.c_str (), true);
        m_cells_by_name.insert (std::make_pair (name, mm_placement_cell.get ()));

      } else {
        mm_placement_cell = cid->second;
      }

    }

  } 

  double mag = 1.0;
  bool mag_set = false;

  double angle_deg = 0.0; // only meaningful if angle < 0
  int angle = 0;
  bool mirror = false;

  if (r == 18) {

    if (m & 0x04) {
      mag = get_real ();
      if (fabs (mag - 1.0) > 1e-6) {
        mag_set = true;
      }
    }

    if (m & 0x02) {
      angle_deg = get_real ();
      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-6) {
        angle = -1; // indicates arbitrary orientation. Take angle_deg instead
      } else {
        if (angle < 0) {
          angle += ((4 - 1) - angle) & ~(4 - 1);
        }
        angle = angle % 4;
      }
    }

  } else {
    angle = ((m & 0x06) >> 1);
  }
      
  mirror = (m & 0x01) != 0;

  if (m & 0x20) {
    db::Coord x;
    get (x);
    if (xy_absolute) {
      mm_placement_x = x;
    } else {
      mm_placement_x = x + mm_placement_x.get ();
    }
  }

  if (m & 0x10) {
    db::Coord y;
    get (y);
    if (xy_absolute) {
      mm_placement_y = y;
    } else {
      mm_placement_y = y + mm_placement_y.get ();
    }
  }

  db::Vector pos (mm_placement_x.get (), mm_placement_y.get ());

  if ((m & 0x8) && read_repetition ()) {

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

    db::Vector a, b;
    size_t na, nb;
    if (mm_repetition.get ().is_regular (a, b, na, nb)) {

      db::CellInstArray inst;

      if (mag_set || angle < 0) {
        inst = db::CellInstArray (db::CellInst (mm_placement_cell.get ()), 
                                  db::ICplxTrans (mag, angle_deg, mirror, pos), layout.array_repository (), a, b, na, nb);
      } else {
        inst = db::CellInstArray (db::CellInst (mm_placement_cell.get ()), 
                                  db::Trans (angle, mirror, pos), layout.array_repository (), a, b, na, nb);
      }

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

    } else {

      RepetitionIterator p = mm_repetition.get ().begin ();
      while (! p.at_end ()) {

        db::CellInstArray inst;

        if (mag_set || angle < 0) {
          inst = db::CellInstArray (db::CellInst (mm_placement_cell.get ()), db::ICplxTrans (mag, angle_deg, mirror, pos + *p));
        } else {
          inst = db::CellInstArray (db::CellInst (mm_placement_cell.get ()), db::Trans (angle, mirror, pos + *p));
        }

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

        ++p;

      }

    }

  } else {

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

    db::CellInstArray inst;

    if (mag_set || angle < 0) {
      inst = db::CellInstArray (db::CellInst (mm_placement_cell.get ()), db::ICplxTrans (mag, angle_deg, mirror, pos));
    } else {
      inst = db::CellInstArray (db::CellInst (mm_placement_cell.get ()), db::Trans (angle, mirror, pos));
    }

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

  }
}

void 
OASISReader::do_read_text (bool xy_absolute,
                           db::cell_index_type cell_index, 
                           db::Layout &layout)
{
  unsigned char m = get_byte ();

  if (m & 0x40) {
    if (m & 0x20) {

      unsigned long id;
      get (id);

      if (m_text_forward_references.find (id) != m_text_forward_references.end ()) {

        mm_text_string.reset ();
        mm_text_string_id = id;

      } else {

        std::map <unsigned long, std::string>::const_iterator tid = m_textstrings.find (id);
        if (tid == m_textstrings.end ()) {

          mm_text_string.reset ();
          mm_text_string_id = id;

          const db::StringRef *string_ref = layout.string_repository ().create_string_ref ();
          m_text_forward_references.insert (std::make_pair (id, string_ref));

        } else {

          mm_text_string = tid->second;

        }

      }

    } else {

      if (m_expect_strict_mode == 1) {
        warn (tl::to_string (QObject::tr ("TEXT strings must be references to TEXTSTRING ids in strict mode")));
      }

      mm_text_string = get_str ();

    }
  } 

  if (m & 0x1) {
    mm_textlayer = get_uint ();
  }

  if (m & 0x2) {
    mm_texttype = get_uint ();
  }

  if (m & 0x10) {
    db::Coord x;
    get (x);
    if (xy_absolute) {
      mm_text_x = x;
    } else {
      mm_text_x = x + mm_text_x.get ();
    }
  }

  if (m & 0x8) {
    db::Coord y;
    get (y);
    if (xy_absolute) {
      mm_text_y = y;
    } else {
      mm_text_y = y + mm_text_y.get ();
    }
  }

  db::Vector pos (mm_text_x.get (), mm_text_y.get ());

  std::pair<bool, unsigned int> ll (false, 0);
  if (m_read_texts) {
    ll = open_dl (layout, LDPair (mm_textlayer.get (), mm_texttype.get ()), m_create_layers);
  }

  if ((m & 0x4) && read_repetition ()) {

    //  TODO: should not read properties if layer is not enabled!
    std::pair<bool, db::properties_id_type> pp = read_element_properties (layout.properties_repository (), false);

    if (ll.first) {

      db::Text text;
      if (mm_text_string_id.is_set ()) {
        text = db::Text (m_text_forward_references.find (mm_text_string_id.get ())->second, db::Trans ());
      } else {
        text = db::Text (mm_text_string.get (), db::Trans ());
      }

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

      const std::vector<db::Vector> *points = 0;

      //  If the repetion is a regular one, convert the repetition into
      //  a shape array
      db::Vector a, b;
      size_t na, nb;
      if (! layout.is_editable () && mm_repetition.get ().is_regular (a, b, na, nb)) {

        db::TextPtr text_ptr (text, layout.shape_repository ());

        if (pp.first) {
          cell.shapes (ll.second).insert (db::object_with_properties<db::Shape::text_ptr_array_type> (db::Shape::text_ptr_array_type (text_ptr, db::Disp (pos), layout.array_repository (), a, b, na, nb), pp.second));
        } else {
          cell.shapes (ll.second).insert (db::Shape::text_ptr_array_type (text_ptr, db::Disp (pos), layout.array_repository (), a, b, na, nb));
        }

      } else if (! layout.is_editable () && (points = mm_repetition.get ().is_iterated ()) != 0) {

        db::TextPtr text_ptr (text, layout.shape_repository ());

        //  Create an iterated text array
        db::Shape::text_ptr_array_type::iterated_array_type array;
        array.reserve (points->size () + 1);
        array.insert (db::Vector ());
        array.insert (points->begin (), points->end ());
        array.sort ();
        
        if (pp.first) {
          cell.shapes (ll.second).insert (db::object_with_properties<db::Shape::text_ptr_array_type> (db::Shape::text_ptr_array_type (text_ptr, db::Disp (pos), layout.array_repository ().insert (array)), pp.second));
        } else {
          cell.shapes (ll.second).insert (db::Shape::text_ptr_array_type (text_ptr, db::Disp (pos), layout.array_repository ().insert (array)));
        }

      } else {

        RepetitionIterator p = mm_repetition.get ().begin ();
        db::TextRef text_ref (text, layout.shape_repository ());
        while (! p.at_end ()) {
          if (pp.first) {
            cell.shapes (ll.second).insert (db::TextRefWithProperties (text_ref.transformed (db::Disp (pos + *p)), pp.second));
          } else {
            cell.shapes (ll.second).insert (text_ref.transformed (db::Disp (pos + *p)));
          }
          ++p;
        }

      }

    }

  } else {

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

    if (ll.first) {

      db::Text text;
      if (mm_text_string_id.is_set ()) {
        text = db::Text (m_text_forward_references.find (mm_text_string_id.get ())->second, db::Trans (pos));
      } else {
        text = db::Text (mm_text_string.get (), db::Trans (pos));
      }

      if (pp.first) {
        layout.cell (cell_index).shapes (ll.second).insert (db::TextRefWithProperties (db::TextRef (text, layout.shape_repository ()), pp.second));
      } else {
        layout.cell (cell_index).shapes (ll.second).insert (db::TextRef (text, layout.shape_repository ()));
      }

    }

  }
}

void 
OASISReader::do_read_rectangle (bool xy_absolute,
                                db::cell_index_type cell_index, 
                                db::Layout &layout)
{
  unsigned char m = get_byte ();

  if (m & 0x1) {
    mm_layer = get_uint ();
  }

  if (m & 0x2) {
    mm_datatype = get_uint ();
  }

  if (m & 0x40) {
    mm_geometry_w = get_ucoord_as_distance ();
  } 
  if (m & 0x80) {
    mm_geometry_h = mm_geometry_w; // TODO: really?
  } else {
    if (m & 0x20) {
      mm_geometry_h = get_ucoord_as_distance ();
    } 
  }

  if (m & 0x10) {
    db::Coord x;
    get (x);
    if (xy_absolute) {
      mm_geometry_x = x;
    } else {
      mm_geometry_x = x + mm_geometry_x.get ();
    }
  }

  if (m & 0x8) {
    db::Coord y;
    get (y);
    if (xy_absolute) {
      mm_geometry_y = y;
    } else {
      mm_geometry_y = y + mm_geometry_y.get ();
    }
  }

  db::Box box (db::Point (mm_geometry_x.get (), mm_geometry_y.get ()),
               db::Point (mm_geometry_x.get () + mm_geometry_w.get (), mm_geometry_y.get () + mm_geometry_h.get ()));

  std::pair<bool, unsigned int> ll = open_dl (layout, LDPair (mm_layer.get (), mm_datatype.get ()), m_create_layers);

  if ((m & 0x4) && read_repetition ()) {

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

    if (ll.first) {

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

      const std::vector<db::Vector> *points = 0;

      //  If the repetion is a regular one, convert the repetition into
      //  a box array
      db::Vector a, b;
      size_t na, nb;
      if (! layout.is_editable () && mm_repetition.get ().is_regular (a, b, na, nb)) {

        //  Create a box array
        if (pp.first) {
          cell.shapes (ll.second).insert (db::object_with_properties<db::Shape::box_array_type> (db::Shape::box_array_type (box, db::UnitTrans (), layout.array_repository (), a, b, na, nb), pp.second));
        } else {
          cell.shapes (ll.second).insert (db::Shape::box_array_type (box, db::UnitTrans (), layout.array_repository (), a, b, na, nb));
        }

      } else if (! layout.is_editable () && (points = mm_repetition.get ().is_iterated ()) != 0) {

        //  Create an iterated box array
        db::Shape::box_array_type::iterated_array_type array;
        array.reserve (points->size () + 1);
        array.insert (db::Vector ());
        array.insert (points->begin (), points->end ());
        array.sort ();
        
        if (pp.first) {
          cell.shapes (ll.second).insert (db::object_with_properties<db::Shape::box_array_type> (db::Shape::box_array_type (box, db::UnitTrans (), layout.array_repository ().insert (array)), pp.second));
        } else {
          cell.shapes (ll.second).insert (db::Shape::box_array_type (box, db::UnitTrans (), layout.array_repository ().insert (array)));
        }

      } else {

        //  convert the OASIS record into the rectangle one by one.
        RepetitionIterator p = mm_repetition.get ().begin ();
        while (! p.at_end ()) {
          if (pp.first) {
            cell.shapes (ll.second).insert (db::BoxWithProperties (box.moved (*p), pp.second));
          } else {
            cell.shapes (ll.second).insert (box.moved (*p));
          }
          ++p;
        }

      }

    }

  } else {
    
    std::pair<bool, db::properties_id_type> pp = read_element_properties (layout.properties_repository (), false);

    if (ll.first) {

      db::Cell &cell = layout.cell (cell_index);
      if (pp.first) {
        cell.shapes (ll.second).insert (db::BoxWithProperties (box, pp.second));
      } else {
        cell.shapes (ll.second).insert (box);
      }

    }

  }
}

void  
OASISReader::do_read_polygon (bool xy_absolute, db::cell_index_type cell_index, db::Layout &layout)
{
  unsigned char m = get_byte ();

  if (m & 0x1) {
    mm_layer = get_uint ();
  }

  if (m & 0x2) {
    mm_datatype = get_uint ();
  }

  if (m & 0x20) {
    read_pointlist (mm_polygon_point_list, true);
  }

  if (m & 0x10) {
    db::Coord x;
    get (x);
    if (xy_absolute) {
      mm_geometry_x = x;
    } else {
      mm_geometry_x = x + mm_geometry_x.get ();
    }
  }

  if (m & 0x8) {
    db::Coord y;
    get (y);
    if (xy_absolute) {
      mm_geometry_y = y;
    } else {
      mm_geometry_y = y + mm_geometry_y.get ();
    }
  }

  db::Vector pos (mm_geometry_x.get (), mm_geometry_y.get ());

  std::pair<bool, unsigned int> ll = open_dl (layout, LDPair (mm_layer.get (), mm_datatype.get ()), m_create_layers);

  if ((m & 0x4) && read_repetition ()) {

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

    if (ll.first) {

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

      if (mm_polygon_point_list.get ().size () < 3) {
        warn (tl::to_string (QObject::tr ("POLYGON with less than 3 points ignored")));
      } else {

        //  convert the OASIS record into the polygon.
        db::SimplePolygon poly;
        poly.assign_hull (mm_polygon_point_list.get ().begin (), mm_polygon_point_list.get ().end (), false /*no compression*/);

        const std::vector<db::Vector> *points = 0;

        //  If the repetion is a regular one, convert the repetition into
        //  a shape array
        db::Vector a, b;
        size_t na, nb;
        if (! layout.is_editable () && mm_repetition.get ().is_regular (a, b, na, nb)) {

          //  creating a SimplePolygonPtr is most efficient with a normalized polygon because no displacement is provided
          db::Vector d (poly.box ().lower_left () - db::Point ());
          poly.move (-d);
          db::SimplePolygonPtr poly_ptr (poly, layout.shape_repository ());

          if (pp.first) {
            cell.shapes (ll.second).insert (db::object_with_properties<db::array<db::SimplePolygonPtr, db::Disp> > (db::array<db::SimplePolygonPtr, db::Disp> (poly_ptr, db::Disp (d + pos), layout.array_repository (), a, b, na, nb), pp.second));
          } else {
            cell.shapes (ll.second).insert (db::array<db::SimplePolygonPtr, db::Disp> (poly_ptr, db::Disp (d + pos), layout.array_repository (), a, b, na, nb));
          }

        } else if (! layout.is_editable () && (points = mm_repetition.get ().is_iterated ()) != 0) {

          db::Vector d (poly.box ().lower_left () - db::Point ());
          poly.move (-d);
          db::SimplePolygonPtr poly_ptr (poly, layout.shape_repository ());

          //  Create an iterated simple polygon array
          db::Shape::simple_polygon_ptr_array_type::iterated_array_type array;
          array.reserve (points->size () + 1);
          array.insert (db::Vector ());
          array.insert (points->begin (), points->end ());
          array.sort ();
          
          if (pp.first) {
            cell.shapes (ll.second).insert (db::object_with_properties<db::Shape::simple_polygon_ptr_array_type> (db::Shape::simple_polygon_ptr_array_type (poly_ptr, db::Disp (d + pos), layout.array_repository ().insert (array)), pp.second));
          } else {
            cell.shapes (ll.second).insert (db::Shape::simple_polygon_ptr_array_type (poly_ptr, db::Disp (d + pos), layout.array_repository ().insert (array)));
          }

        } else {

          db::SimplePolygonRef poly_ref (poly, layout.shape_repository ());

          RepetitionIterator p = mm_repetition.get ().begin ();
          while (! p.at_end ()) {
            if (pp.first) {
              cell.shapes (ll.second).insert (db::SimplePolygonRefWithProperties (poly_ref.transformed (db::Disp (pos + *p)), pp.second));
            } else {
              cell.shapes (ll.second).insert (poly_ref.transformed (db::Disp (pos + *p)));
            }
            ++p;
          }
      
        }

      }

    }

  } else {

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

    if (ll.first) {

      if (mm_polygon_point_list.get ().size () < 3) {
        warn (tl::to_string (QObject::tr ("POLYGON with less than 3 points ignored")));
      } else {

        //  convert the OASIS record into the polygon.
        db::SimplePolygon poly;
        poly.assign_hull (mm_polygon_point_list.get ().begin (), mm_polygon_point_list.get ().end (), false /*no compression*/);
        db::SimplePolygonRef poly_ref (poly, layout.shape_repository ());

        if (pp.first) {
          layout.cell (cell_index).shapes (ll.second).insert (db::SimplePolygonRefWithProperties (poly_ref.transformed (db::Disp (pos)), pp.second));
        } else {
          layout.cell (cell_index).shapes (ll.second).insert (poly_ref.transformed (db::Disp (pos)));
        }

      }

    }

  }
}

void  
OASISReader::do_read_path (bool xy_absolute, db::cell_index_type cell_index, db::Layout &layout)
{
  unsigned char m = get_byte ();

  if (m & 0x1) {
    mm_layer = get_uint ();
  }

  if (m & 0x2) {
    mm_datatype = get_uint ();
  }

  if (m & 0x40) {
    mm_path_halfwidth = get_ucoord_as_distance ();
  }

  if (m & 0x80) {

    unsigned int e = get_uint ();
    if ((e & 0x0c) == 0x0c) {
      mm_path_start_extension = get_coord ();
    } else if ((e & 0x0c) == 0x04) {
      mm_path_start_extension = 0; //  TODO: is setting the start extension modal variable correct here?
    } else if ((e & 0x0c) == 0x08) {
      mm_path_start_extension = mm_path_halfwidth.get (); //  TODO: is setting the start extension modal variable correct here?
    }
    if ((e & 0x03) == 0x03) {
      mm_path_end_extension = get_coord ();
    } else if ((e & 0x03) == 0x01) {
      mm_path_end_extension = 0; //  TODO: is setting the start extension modal variable correct here?
    } else if ((e & 0x03) == 0x02) {
      mm_path_end_extension = mm_path_halfwidth.get (); //  TODO: is setting the start extension modal variable correct here?
    }

  }

  if (m & 0x20) {
    read_pointlist (mm_path_point_list, false);
  }

  if (m & 0x10) {
    db::Coord x;
    get (x);
    if (xy_absolute) {
      mm_geometry_x = x;
    } else {
      mm_geometry_x = x + mm_geometry_x.get ();
    }
  }

  if (m & 0x8) {
    db::Coord y;
    get (y);
    if (xy_absolute) {
      mm_geometry_y = y;
    } else {
      mm_geometry_y = y + mm_geometry_y.get ();
    }
  }

  db::Vector pos (mm_geometry_x.get (), mm_geometry_y.get ());

  std::pair<bool, unsigned int> ll = open_dl (layout, LDPair (mm_layer.get (), mm_datatype.get ()), m_create_layers);

  if ((m & 0x4) && read_repetition ()) {

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

    if (ll.first) {

      if (mm_path_point_list.get ().size () < 2) {
        warn (tl::to_string (QObject::tr ("POLYGON with less than 2 points ignored")));
      } else {

        //  convert the OASIS record into the path.
        db::Path path;
        path.width (2 * mm_path_halfwidth.get ());
        path.extensions (mm_path_start_extension.get (), mm_path_end_extension.get ());
        path.assign (mm_path_point_list.get ().begin (), mm_path_point_list.get ().end ());

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

        const std::vector<db::Vector> *points = 0;

        //  If the repetion is a regular one, convert the repetition into
        //  a shape array
        db::Vector a, b;
        size_t na, nb;
        if (! layout.is_editable () && mm_repetition.get ().is_regular (a, b, na, nb)) {

          //  creating a PathPtr is most efficient with a normalized path because no displacement is provided
          db::Vector d (*path.begin ());
          path.move (-d);
          db::PathPtr path_ptr (path, layout.shape_repository ());

          if (pp.first) {
            cell.shapes (ll.second).insert (db::object_with_properties<db::array<db::PathPtr, db::Disp> > (db::array<db::PathPtr, db::Disp> (path_ptr, db::Disp (d + pos), layout.array_repository (), a, b, na, nb), pp.second));
          } else {
            cell.shapes (ll.second).insert (db::array<db::PathPtr, db::Disp> (path_ptr, db::Disp (d + pos), layout.array_repository (), a, b, na, nb));
          }

        } else if (! layout.is_editable () && (points = mm_repetition.get ().is_iterated ()) != 0) {

          db::Vector d (*path.begin () - db::Point ());
          path.move (-d);
          db::PathPtr path_ptr (path, layout.shape_repository ());

          //  Create an iterated simple polygon array
          db::Shape::path_ptr_array_type::iterated_array_type array;
          array.reserve (points->size () + 1);
          array.insert (db::Vector ());
          array.insert (points->begin (), points->end ());
          array.sort ();
          
          if (pp.first) {
            cell.shapes (ll.second).insert (db::object_with_properties<db::Shape::path_ptr_array_type> (db::Shape::path_ptr_array_type (path_ptr, db::Disp (d + pos), layout.array_repository ().insert (array)), pp.second));
          } else {
            cell.shapes (ll.second).insert (db::Shape::path_ptr_array_type (path_ptr, db::Disp (d + pos), layout.array_repository ().insert (array)));
          }

        } else {

          db::PathRef path_ref (path, layout.shape_repository ());

          RepetitionIterator p = mm_repetition.get ().begin ();
          while (! p.at_end ()) {
            if (pp.first) {
              cell.shapes (ll.second).insert (db::PathRefWithProperties (path_ref.transformed (db::Disp (pos + *p)), pp.second));
            } else {
              cell.shapes (ll.second).insert (path_ref.transformed (db::Disp (pos + *p)));
            }
            ++p;
          }

        }
        
      }

    }

  } else {

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

    if (ll.first) {

      if (mm_path_point_list.get ().size () < 2) {
        warn (tl::to_string (QObject::tr ("PATH with less than 2 points ignored")));
      } else {

        //  convert the OASIS record into the path.
        db::Path path;
        path.width (2 * mm_path_halfwidth.get ());
        path.extensions (mm_path_start_extension.get (), mm_path_end_extension.get ());
        path.assign (mm_path_point_list.get ().begin (), mm_path_point_list.get ().end ());
        db::PathRef path_ref (path, layout.shape_repository ());

        if (pp.first) {
          layout.cell (cell_index).shapes (ll.second).insert (db::PathRefWithProperties (path_ref.transformed (db::Disp (pos)), pp.second));
        } else {
          layout.cell (cell_index).shapes (ll.second).insert (path_ref.transformed (db::Disp (pos)));
        }

      }

    }

  }
}

void  
OASISReader::do_read_trapezoid (unsigned char r, bool xy_absolute,db::cell_index_type cell_index, db::Layout &layout)
{
  unsigned char m = get_byte ();

  if (m & 0x1) {
    mm_layer = get_uint ();
  }

  if (m & 0x2) {
    mm_datatype = get_uint ();
  }

  if (m & 0x40) {
    mm_geometry_w = get_ucoord_as_distance ();
  }

  if (m & 0x20) {
    mm_geometry_h = get_ucoord_as_distance ();
  }

  db::Coord delta_a = 0, delta_b = 0;
  if (r == 23 || r == 24) {
    delta_a = get_coord ();
  }
  if (r == 23 || r == 25) {
    delta_b = get_coord ();
  }

  if (m & 0x10) {
    db::Coord x;
    get (x);
    if (xy_absolute) {
      mm_geometry_x = x;
    } else {
      mm_geometry_x = x + mm_geometry_x.get ();
    }
  }

  if (m & 0x8) {
    db::Coord y;
    get (y);
    if (xy_absolute) {
      mm_geometry_y = y;
    } else {
      mm_geometry_y = y + mm_geometry_y.get ();
    }
  }

  db::Vector pos (mm_geometry_x.get (), mm_geometry_y.get ());

  std::pair<bool, unsigned int> ll = open_dl (layout, LDPair (mm_layer.get (), mm_datatype.get ()), m_create_layers);

  db::Point pts [4];

  if (m & 0x80) {
    //  vertically
    pts [0] = db::Point (0, std::max (delta_a, db::Coord (0)));
    pts [1] = db::Point (0, mm_geometry_h.get () + std::min (delta_b, db::Coord (0)));
    pts [2] = db::Point (mm_geometry_w.get (), mm_geometry_h.get () - std::max (delta_b, db::Coord (0)));
    pts [3] = db::Point (mm_geometry_w.get (), -std::min (delta_a, db::Coord (0)));
  } else {
    //  horizontally
    pts [0] = db::Point (std::max (delta_a, db::Coord (0)), mm_geometry_h.get ());
    pts [1] = db::Point (mm_geometry_w.get () + std::min (delta_b, db::Coord (0)), mm_geometry_h.get ());
    pts [2] = db::Point (mm_geometry_w.get () - std::max (delta_b, db::Coord (0)), db::Coord (0));
    pts [3] = db::Point (-std::min (delta_a, db::Coord (0)), db::Coord (0));
  }

  if ((m & 0x4) && read_repetition ()) {

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

    if (ll.first) {

      //  convert the OASIS record into the polygon.
      db::SimplePolygon poly;
      poly.assign_hull (pts, pts + 4, false /*no compression*/);

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

      const std::vector<db::Vector> *points = 0;

      //  If the repetion is a regular one, convert the repetition into
      //  a shape array
      db::Vector a, b;
      size_t na, nb;
      if (! layout.is_editable () && mm_repetition.get ().is_regular (a, b, na, nb)) {

        //  creating a SimplePolygonPtr is most efficient with a normalized polygon because no displacement is provided
        db::Vector d (poly.box ().lower_left ());
        poly.move (-d);
        db::SimplePolygonPtr poly_ptr (poly, layout.shape_repository ());

        if (pp.first) {
          cell.shapes (ll.second).insert (db::object_with_properties<db::array<db::SimplePolygonPtr, db::Disp> > (db::array<db::SimplePolygonPtr, db::Disp> (poly_ptr, db::Disp (d + pos), layout.array_repository (), a, b, na, nb), pp.second));
        } else {
          cell.shapes (ll.second).insert (db::array<db::SimplePolygonPtr, db::Disp> (poly_ptr, db::Disp (d + pos), layout.array_repository (), a, b, na, nb));
        }

      } else if (! layout.is_editable () && (points = mm_repetition.get ().is_iterated ()) != 0) {

        db::Vector d (poly.box ().lower_left () - db::Point ());
        poly.move (-d);
        db::SimplePolygonPtr poly_ptr (poly, layout.shape_repository ());

        //  Create an iterated simple polygon array
        db::Shape::simple_polygon_ptr_array_type::iterated_array_type array;
        array.reserve (points->size () + 1);
        array.insert (db::Vector ());
        array.insert (points->begin (), points->end ());
        array.sort ();
        
        if (pp.first) {
          cell.shapes (ll.second).insert (db::object_with_properties<db::Shape::simple_polygon_ptr_array_type> (db::Shape::simple_polygon_ptr_array_type (poly_ptr, db::Disp (d + pos), layout.array_repository ().insert (array)), pp.second));
        } else {
          cell.shapes (ll.second).insert (db::Shape::simple_polygon_ptr_array_type (poly_ptr, db::Disp (d + pos), layout.array_repository ().insert (array)));
        }

      } else {

        db::SimplePolygonRef poly_ref (poly, layout.shape_repository ());

        RepetitionIterator p = mm_repetition.get ().begin ();
        while (! p.at_end ()) {
          if (pp.first) {
            cell.shapes (ll.second).insert (db::SimplePolygonRefWithProperties (poly_ref.transformed (db::Disp (pos + *p)), pp.second));
          } else {
            cell.shapes (ll.second).insert (poly_ref.transformed (db::Disp (pos + *p)));
          }
          ++p;
        }

      }

    }

  } else {

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

    if (ll.first) {

      //  convert the OASIS record into the polygon.
      db::SimplePolygon poly;
      poly.assign_hull (pts, pts + 4, false /*no compression*/);
      db::SimplePolygonRef poly_ref (poly, layout.shape_repository ());

      if (pp.first) {
        layout.cell (cell_index).shapes (ll.second).insert (SimplePolygonRefWithProperties (poly_ref.transformed (db::Disp (pos)), pp.second));
      } else {
        layout.cell (cell_index).shapes (ll.second).insert (poly_ref.transformed (db::Disp (pos)));
      }

    }

  }
}

void  
OASISReader::do_read_ctrapezoid (bool xy_absolute,db::cell_index_type cell_index, db::Layout &layout)
{
  unsigned char m = get_byte ();

  if (m & 0x1) {
    mm_layer = get_uint ();
  }

  if (m & 0x2) {
    mm_datatype = get_uint ();
  }

  if (m & 0x80) {
    mm_ctrapezoid_type = get_uint ();
  }

  if (m & 0x40) {
    mm_geometry_w = get_ucoord_as_distance ();
  }

  if (m & 0x20) {
    mm_geometry_h = get_ucoord_as_distance ();
  }

  if (m & 0x10) {
    db::Coord x;
    get (x);
    if (xy_absolute) {
      mm_geometry_x = x;
    } else {
      mm_geometry_x = x + mm_geometry_x.get ();
    }
  }

  if (m & 0x8) {
    db::Coord y;
    get (y);
    if (xy_absolute) {
      mm_geometry_y = y;
    } else {
      mm_geometry_y = y + mm_geometry_y.get ();
    }
  }

  db::Vector pos (mm_geometry_x.get (), mm_geometry_y.get ());

  std::pair<bool, unsigned int> ll = open_dl (layout, LDPair (mm_layer.get (), mm_datatype.get ()), m_create_layers);

  db::Point pts [4];

  static db::Coord ctraps_table[][4][4] = {
    //  type 0
    {
      { 0, 0, 0, 0 },  // x=0*w+0*h, y=0*w+0*h ...
      { 0, 0, 0, 1 },
      { 1, -1, 0, 1 },
      { 1, 0, 0, 0 }
    },
    //  type 1
    {
      { 0, 0, 0, 0 },  
      { 0, 0, 0, 1 },
      { 1, 0, 0, 1 },
      { 1, -1, 0, 0 }
    },
    //  type 2
    {
      { 0, 0, 0, 0 },  
      { 0, 1, 0, 1 },
      { 1, 0, 0, 1 },
      { 1, 0, 0, 0 }
    },
    //  type 3
    {
      { 0, 1, 0, 0 },  
      { 0, 0, 0, 1 },
      { 1, 0, 0, 1 },
      { 1, 0, 0, 0 }
    },
    //  type 4
    {
      { 0, 0, 0, 0 },
      { 0, 1, 0, 1 },
      { 1, -1, 0, 1 },
      { 1, 0, 0, 0 }
    },
    //  type 5
    {
      { 0, 1, 0, 0 },  
      { 0, 0, 0, 1 },
      { 1, 0, 0, 1 },
      { 1, -1, 0, 0 }
    },
    //  type 6
    {
      { 0, 0, 0, 0 },  
      { 0, 1, 0, 1 },
      { 1, 0, 0, 1 },
      { 1, -1, 0, 0 }
    },
    //  type 7
    {
      { 0, 1, 0, 0 },  
      { 0, 0, 0, 1 },
      { 1, -1, 0, 1 },
      { 1, 0, 0, 0 }
    },
    //  type 8
    {
      { 0, 0, 0, 0 },  
      { 0, 0, 0, 1 },
      { 1, 0, -1, 1 },
      { 1, 0, 0, 0 }
    },
    //  type 9
    {
      { 0, 0, 0, 0 },  
      { 0, 0, -1, 1 },
      { 1, 0, 0, 1 },
      { 1, 0, 0, 0 }
    },
    //  type 10
    {
      { 0, 0, 0, 0 },  
      { 0, 0, 0, 1 },
      { 1, 0, 0, 1 },
      { 1, 0, 1, 0 }
    },
    //  type 11
    {
      { 0, 0, 1, 0 },  
      { 0, 0, 0, 1 },
      { 1, 0, 0, 1 },
      { 1, 0, 0, 0 }
    },
    //  type 12
    {
      { 0, 0, 0, 0 },  
      { 0, 0, 0, 1 },
      { 1, 0, -1, 1 },
      { 1, 0, 1, 0 }
    },
    //  type 13
    {
      { 0, 0, 1, 0 },  
      { 0, 0, -1, 1 },
      { 1, 0, 0, 1 },
      { 1, 0, 0, 0 }
    },
    //  type 14
    {
      { 0, 0, 0, 0 },  
      { 0, 0, -1, 1 },
      { 1, 0, 0, 1 },
      { 1, 0, 1, 0 }
    },
    //  type 15
    {
      { 0, 0, 1, 0 },  
      { 0, 0, 0, 1 },
      { 1, 0, -1, 1 },
      { 1, 0, 0, 0 }
    },
    //  type 16
    {
      { 0, 0, 0, 0 },  
      { 0, 0, 1, 0 },
      { 1, 0, 0, 0 },
      { 0, 0, 0, 0 }
    },
    //  type 17
    {
      { 0, 0, 0, 0 },  
      { 0, 0, 1, 0 },
      { 1, 0, 1, 0 },
      { 0, 0, 0, 0 }
    },
    //  type 18
    {
      { 0, 0, 0, 0 },  
      { 1, 0, 1, 0 },
      { 1, 0, 0, 0 },
      { 0, 0, 0, 0 }
    },
    //  type 19
    {
      { 0, 0, 1, 0 },  
      { 1, 0, 1, 0 },
      { 1, 0, 0, 0 },
      { 0, 0, 1, 0 }
    },
    //  type 20
    {
      { 0, 0, 0, 0 },  
      { 0, 1, 0, 1 },
      { 0, 2, 0, 0 },
      { 0, 0, 0, 0 }
    },
    //  type 21
    {
      { 0, 0, 0, 1 },  
      { 0, 2, 0, 1 },
      { 0, 1, 0, 0 },
      { 0, 0, 0, 1 }
    },
    //  type 22
    {
      { 0, 0, 0, 0 },  
      { 0, 0, 2, 0 },
      { 1, 0, 1, 0 },
      { 0, 0, 0, 0 }
    },
    //  type 23
    {
      { 1, 0, 0, 0 },  
      { 0, 0, 1, 0 },
      { 1, 0, 2, 0 },
      { 1, 0, 0, 0 }
    },
    //  type 24
    {
      { 0, 0, 0, 0 },  
      { 0, 0, 0, 1 },
      { 1, 0, 0, 1 },
      { 1, 0, 0, 0 }
    },
    //  type 25
    {
      { 0, 0, 0, 0 },  
      { 0, 0, 1, 0 },
      { 1, 0, 1, 0 },
      { 1, 0, 0, 0 }
    },
  };

  if (mm_ctrapezoid_type.get () >= sizeof (ctraps_table) / sizeof (ctraps_table [0])) {
    error (tl::sprintf (tl::to_string (QObject::tr ("Invalid CTRAPEZOID type %d")), int (mm_ctrapezoid_type.get ())));
  }

  db::Coord w = 0, h = 0;

  for (unsigned i = 0; i < 4; ++i) {

    db::Coord *m = ctraps_table [mm_ctrapezoid_type.get ()][i];

    db::Coord x = 0;
    if (m[0] != 0) x += m[0] * mm_geometry_w.get ();
    if (m[1] != 0) x += m[1] * mm_geometry_h.get ();

    db::Coord y = 0;
    if (m[2] != 0) y += m[2] * mm_geometry_w.get ();
    if (m[3] != 0) y += m[3] * mm_geometry_h.get ();

    pts [i] = db::Point (x, y);
    
    if (x > w) w = x;
    if (y > h) h = y;

  }

  //  set modal variables to the bbox of the shape
  mm_geometry_w = w;
  mm_geometry_h = h;

  unsigned int npts = 4;
  if (pts [npts - 1] == pts [0]) {
    --npts;
  }

  if ((m & 0x4) && read_repetition ()) {

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

    if (ll.first) {

      //  convert the OASIS record into the polygon.
      db::SimplePolygon poly;
      poly.assign_hull (pts, pts + npts, false /*no compression*/);

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

      const std::vector<db::Vector> *points = 0;

      //  If the repetion is a regular one, convert the repetition into
      //  a shape array
      db::Vector a, b;
      size_t na, nb;
      if (! layout.is_editable () && mm_repetition.get ().is_regular (a, b, na, nb)) {

        db::Vector d (poly.box ().lower_left () - db::Point ());
        poly.move (-d);
        db::SimplePolygonPtr poly_ptr (poly, layout.shape_repository ());

        if (pp.first) {
          cell.shapes (ll.second).insert (db::object_with_properties<db::array<db::SimplePolygonPtr, db::Disp> > (db::array<db::SimplePolygonPtr, db::Disp> (poly_ptr, db::Disp (d + pos), layout.array_repository (), a, b, na, nb), pp.second));
        } else {
          cell.shapes (ll.second).insert (db::array<db::SimplePolygonPtr, db::Disp> (poly_ptr, db::Disp (d + pos), layout.array_repository (), a, b, na, nb));
        }

      } else if (! layout.is_editable () && (points = mm_repetition.get ().is_iterated ()) != 0) {

        db::Vector d (poly.box ().lower_left () - db::Point ());
        poly.move (-d);
        db::SimplePolygonPtr poly_ptr (poly, layout.shape_repository ());

        //  Create an iterated simple polygon array
        db::Shape::simple_polygon_ptr_array_type::iterated_array_type array;
        array.reserve (points->size () + 1);
        array.insert (db::Vector ());
        array.insert (points->begin (), points->end ());
        array.sort ();
        
        if (pp.first) {
          cell.shapes (ll.second).insert (db::object_with_properties<db::Shape::simple_polygon_ptr_array_type> (db::Shape::simple_polygon_ptr_array_type (poly_ptr, db::Disp (d + pos), layout.array_repository ().insert (array)), pp.second));
        } else {
          cell.shapes (ll.second).insert (db::Shape::simple_polygon_ptr_array_type (poly_ptr, db::Disp (d + pos), layout.array_repository ().insert (array)));
        }

      } else {

        db::SimplePolygonRef poly_ref (poly, layout.shape_repository ());

        RepetitionIterator p = mm_repetition.get ().begin ();
        while (! p.at_end ()) {
          if (pp.first) {
            cell.shapes (ll.second).insert (db::SimplePolygonRefWithProperties (poly_ref.transformed (db::Disp (pos + *p)), pp.second));
          } else {
            cell.shapes (ll.second).insert (poly_ref.transformed (db::Disp (pos + *p)));
          }
          ++p;
        }

      }

    }

  } else {

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

    if (ll.first) {

      //  convert the OASIS record into the polygon.
      db::SimplePolygon poly;
      poly.assign_hull (pts, pts + npts, false /*no compression*/);
      db::SimplePolygonRef poly_ref (poly, layout.shape_repository ());

      if (pp.first) {
        layout.cell (cell_index).shapes (ll.second).insert (db::SimplePolygonRefWithProperties (poly_ref.transformed (db::Disp (pos)), pp.second));
      } else {
        layout.cell (cell_index).shapes (ll.second).insert (poly_ref.transformed (db::Disp (pos)));
      }

    }

  }
}

void  
OASISReader::do_read_circle (bool xy_absolute, db::cell_index_type cell_index, db::Layout &layout)
{
  unsigned char m = get_byte ();

  if (m & 0x1) {
    mm_layer = get_uint ();
  }

  if (m & 0x2) {
    mm_datatype = get_uint ();
  }

  if (m & 0x20) {
    mm_circle_radius = get_ucoord_as_distance ();
  }

  if (m & 0x10) {
    db::Coord x;
    get (x);
    if (xy_absolute) {
      mm_geometry_x = x;
    } else {
      mm_geometry_x = x + mm_geometry_x.get ();
    }
  }

  if (m & 0x8) {
    db::Coord y;
    get (y);
    if (xy_absolute) {
      mm_geometry_y = y;
    } else {
      mm_geometry_y = y + mm_geometry_y.get ();
    }
  }

  db::Vector pos (mm_geometry_x.get (), mm_geometry_y.get ());

  std::pair<bool, unsigned int> ll = open_dl (layout, LDPair (mm_layer.get (), mm_datatype.get ()), m_create_layers);

  //  ignore this circle if the radius is zero
  if (mm_circle_radius.get () <= 0) {
    ll.first = false;
  }

  if ((m & 0x4) && read_repetition ()) {

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

    if (ll.first) {

      //  convert the OASIS circle into a single-point path.
      db::Path path;
      path.width (2 * mm_circle_radius.get ());
      path.extensions (mm_circle_radius.get (), mm_circle_radius.get ());
      path.round (true);
      db::Point p0 (0, 0);
      path.assign (&p0, &p0 + 1);

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

      const std::vector<db::Vector> *points = 0;

      //  If the repetion is a regular one, convert the repetition into
      //  a shape array
      db::Vector a, b;
      size_t na, nb;
      if (! layout.is_editable () && mm_repetition.get ().is_regular (a, b, na, nb)) {

        //  creating a PathPtr is most efficient with a normalized path because no displacement is provided
        db::PathPtr path_ptr (path, layout.shape_repository ());

        if (pp.first) {
          cell.shapes (ll.second).insert (db::object_with_properties<db::array<db::PathPtr, db::Disp> > (db::array<db::PathPtr, db::Disp> (path_ptr, db::Disp (pos), layout.array_repository (), a, b, na, nb), pp.second));
        } else {
          cell.shapes (ll.second).insert (db::array<db::PathPtr, db::Disp> (path_ptr, db::Disp (pos), layout.array_repository (), a, b, na, nb));
        }

      } else if (! layout.is_editable () && (points = mm_repetition.get ().is_iterated ()) != 0) {

        db::PathPtr path_ptr (path, layout.shape_repository ());

        //  Create an iterated simple polygon array
        db::Shape::path_ptr_array_type::iterated_array_type array;
        array.reserve (points->size () + 1);
        array.insert (db::Vector ());
        array.insert (points->begin (), points->end ());
        array.sort ();
        
        if (pp.first) {
          cell.shapes (ll.second).insert (db::object_with_properties<db::Shape::path_ptr_array_type> (db::Shape::path_ptr_array_type (path_ptr, db::Disp (pos), layout.array_repository ().insert (array)), pp.second));
        } else {
          cell.shapes (ll.second).insert (db::Shape::path_ptr_array_type (path_ptr, db::Disp (pos), layout.array_repository ().insert (array)));
        }

      } else {

        db::PathRef path_ref (path, layout.shape_repository ());

        RepetitionIterator p = mm_repetition.get ().begin ();
        while (! p.at_end ()) {
          if (pp.first) {
            cell.shapes (ll.second).insert (db::PathRefWithProperties (path_ref.transformed (db::Disp (pos + *p)), pp.second));
          } else {
            cell.shapes (ll.second).insert (path_ref.transformed (db::Disp (pos + *p)));
          }
          ++p;
        }

      }

    }

  } else {

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

    if (ll.first) {

      //  convert the OASIS circle into a single-point path.
      db::Path path;
      path.width (2 * mm_circle_radius.get ());
      path.extensions (mm_circle_radius.get (), mm_circle_radius.get ());
      path.round (true);
      db::Point p0 (0, 0);
      path.assign (&p0, &p0 + 1);
      db::PathRef path_ref (path, layout.shape_repository ());

      if (pp.first) {
        layout.cell (cell_index).shapes (ll.second).insert (db::PathRefWithProperties (path_ref.transformed (db::Disp (pos)), pp.second));
      } else {
        layout.cell (cell_index).shapes (ll.second).insert (path_ref.transformed (db::Disp (pos)));
      }

    }

  }
}

void
OASISReader::reset_modal_variables ()
{
  //  reset modal variables
  mm_repetition.reset ();
  mm_placement_cell.reset ();
  mm_placement_x = 0;
  mm_placement_y = 0;
  mm_layer.reset ();
  mm_datatype.reset ();
  mm_textlayer.reset ();
  mm_texttype.reset ();
  mm_text_x = 0;
  mm_text_y = 0;
  mm_text_string.reset ();
  mm_text_string_id.reset ();
  mm_geometry_x = 0;
  mm_geometry_y = 0;
  mm_geometry_w.reset ();
  mm_geometry_h.reset ();
  mm_polygon_point_list.reset ();
  mm_path_halfwidth.reset ();
  mm_path_start_extension.reset ();
  mm_path_end_extension.reset ();
  mm_path_point_list.reset ();
  mm_ctrapezoid_type.reset ();
  mm_circle_radius.reset ();
  mm_last_property_name.reset ();
  mm_last_property_is_sprop.reset ();
  mm_last_value_list.reset ();
}

void 
OASISReader::do_read_cell (db::cell_index_type cell_index, db::Layout &layout)
{
  //  clears current instance list 
  m_instances.clear ();
  m_instances_with_props.clear ();

  m_progress.set (m_stream.pos ());
  m_cellname = layout.cell_name (cell_index);

  bool xy_absolute = true;

  bool has_context = false;
  std::vector <std::string> context_strings;
  db::PropertiesRepository::properties_set cell_properties;

  //  read next record
  while (true) {

    m_progress.set (m_stream.pos ());

    unsigned char r = get_byte ();

    if (r == 0 /*PAD*/) {

      //  simply skip.
      mark_start_table ();

    } else if (r == 15 /*XYABSOLUTE*/) {

      //  switch to absolute mode
      xy_absolute = true;

      mark_start_table ();

    } else if (r == 16 /*XYRELATIVE*/) {

      //  switch to relative mode
      xy_absolute = false;

      mark_start_table ();

    } else if (r == 17 || r == 18 /*PLACEMENT*/) {

      do_read_placement (r, xy_absolute, layout, m_instances, m_instances_with_props);

    } else if (r == 19 /*TEXT*/) {

      do_read_text (xy_absolute, cell_index, layout);

    } else if (r == 20 /*RECTANGLE*/) {

      do_read_rectangle (xy_absolute, cell_index, layout);

    } else if (r == 21 /*POLYGON*/) {

      do_read_polygon (xy_absolute, cell_index, layout);

    } else if (r == 22 /*PATH*/) {

      do_read_path (xy_absolute, cell_index, layout);

    } else if (r == 23 || r == 24 || r == 25 /*TRAPEZOID*/) {

      do_read_trapezoid (r, xy_absolute, cell_index, layout);

    } else if (r == 26 /*CTRAPEZOID*/) {

      do_read_ctrapezoid (xy_absolute, cell_index, layout);

    } else if (r == 27 /*CIRCLE*/) {

      do_read_circle (xy_absolute, cell_index, layout);

    } else if (r == 28 || r == 29 /*PROPERTY*/) {

      if (r == 28 /*PROPERTY*/) {
        read_properties (layout.properties_repository ());
      }

      if (! mm_last_property_is_sprop.get () && mm_last_property_name.get () == m_klayout_context_property_name_id) {
        has_context = true;
        context_strings.reserve (mm_last_value_list.get ().size ());
        for (std::vector<tl::Variant>::const_iterator v = mm_last_value_list.get ().begin (); v != mm_last_value_list.get ().end (); ++v) {
          context_strings.push_back (v->to_string ());
        }
      } else {
        //  store cell properties
        store_last_properties (layout.properties_repository (), cell_properties, true);
      }

      mark_start_table ();

    } else if (r == 32 /*XELEMENT*/) {

      //  read over
      get_ulong ();
      get_str ();

      read_element_properties (layout.properties_repository (), true);

    } else if (r == 33 /*XGEOMETRY*/) {

      //  read over.

      unsigned char m = get_byte ();
      get_ulong ();

      if (m & 0x1) {
        mm_layer = get_uint ();
      }

      if (m & 0x2) {
        mm_datatype = get_uint ();
      }

      //  data payload:
      get_str ();

      if (m & 0x10) {
        db::Coord x;
        get (x);
        if (xy_absolute) {
          mm_geometry_x = x;
        } else {
          mm_geometry_x = x + mm_geometry_x.get ();
        }
      }

      if (m & 0x8) {
        db::Coord y;
        get (y);
        if (xy_absolute) {
          mm_geometry_y = y;
        } else {
          mm_geometry_y = y + mm_geometry_y.get ();
        }
      }

      if ((m & 0x4) && read_repetition ()) {
        //  later: handle XGEOMETRY with repetition
      }

      read_element_properties (layout.properties_repository (), true);

    } else if (r == 34 /*CBLOCK*/) {

      unsigned int type = get_uint ();
      if (type != 0) {
        error (tl::sprintf (tl::to_string (QObject::tr ("Invalid CBLOCK compression type %d")), type));
      }

      get_uint ();  // uncomp-byte-count - not needed
      get_uint ();  // comp-byte-count - not needed

      //  put the stream into deflating mode
      m_stream.inflate ();

    } else {
      //  put the byte back into the stream
      m_stream.unget (1);
      break;
    }

  }

  if (! cell_properties.empty ()) {
    layout.cell (cell_index).prop_id (layout.properties_repository ().properties_id (cell_properties));
  }

  //  insert all instances collected (inserting them once is 
  //  more effective than doing this every time)
  if (! m_instances.empty ()) {
    layout.cell (cell_index).insert (m_instances.begin (), m_instances.end ());
    //  clear immediately, because if the cell is cleared before the instances are deleted, the 
    //  array pointers (living in the repository) may no longer be valid
    m_instances.clear ();
  }
  if (! m_instances_with_props.empty ()) {
    layout.cell (cell_index).insert (m_instances_with_props.begin (), m_instances_with_props.end ());
    //  see above.
    m_instances_with_props.clear ();
  }

  //  Restore proxy cell (link to PCell or Library)
  if (has_context) {
    OASISReaderLayerMapping layer_mapping (this, &layout, m_create_layers);
    layout.recover_proxy_as (cell_index, context_strings.begin (), context_strings.end (), &layer_mapping);
  }

  m_cellname = "";
}

}

