/* KLayout Layout Viewer Copyright (C) 2006-2024 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 "tlStream.h" #include "tlAssert.h" #include "tlException.h" #include "dbLayout.h" #include "dbShape.h" #include "dbPolygonTools.h" #include "dbGDS2Writer.h" #include "dbGDS2.h" #include "dbGDS2Format.h" #include "dbClip.h" #include "dbSaveLayoutOptions.h" #include "dbPolygonGenerators.h" #include #include #include #include namespace db { // ------------------------------------------------------------------ // Limit checking conversion functions static int32_t safe_convert_to_int32 (int32_t value) { return value; } static int32_t safe_convert_to_int32 (uint32_t value) { if (value > std::numeric_limits::max ()) { throw tl::Exception (tl::to_string (tr ("Coordinate overflow"))); } return int32_t (value); } static int32_t safe_convert_to_int32 (int64_t value) { if (value < std::numeric_limits::min ()) { throw tl::Exception (tl::to_string (tr ("Coordinate underflow"))); } if (value > std::numeric_limits::max ()) { throw tl::Exception (tl::to_string (tr ("Coordinate overflow"))); } return int32_t (value); } static int32_t safe_convert_to_int32 (uint64_t value) { if (value > std::numeric_limits::max ()) { throw tl::Exception (tl::to_string (tr ("Coordinate overflow"))); } return int32_t (value); } template static int32_t safe_scale (double sf, T value) { double i = floor (sf * value + 0.5); if (i < double (std::numeric_limits::min ())) { throw tl::Exception (tl::to_string (tr ("Scaling failed: coordinate underflow"))); } if (i > double (std::numeric_limits::max ())) { throw tl::Exception (tl::to_string (tr ("Scaling failed: coordinate overflow"))); } return int32_t (i); } template inline int32_t scale (double sf, T value) { if (sf == 1.0) { return safe_convert_to_int32 (value); } else { return safe_scale (sf, value); } } static uint16_t safe_convert_to_uint16 (int16_t value) { // we accept this as GDS is not well defined here ... return value; } static uint16_t safe_convert_to_uint16 (uint16_t value) { return value; } static uint16_t safe_convert_to_uint16 (int32_t value) { if (value < 0) { throw tl::Exception (tl::to_string (tr ("Short unsigned integer underflow"))); } if (value > std::numeric_limits::max ()) { throw tl::Exception (tl::to_string (tr ("Short unsigned integer overflow"))); } return uint16_t (value); } static uint16_t safe_convert_to_uint16 (uint32_t value) { if (value > std::numeric_limits::max ()) { throw tl::Exception (tl::to_string (tr ("Short unsigned integer overflow"))); } return uint16_t (value); } static uint16_t safe_convert_to_uint16 (int64_t value) { if (value < 0) { throw tl::Exception (tl::to_string (tr ("Short unsigned integer underflow"))); } if (value > std::numeric_limits::max ()) { throw tl::Exception (tl::to_string (tr ("Short unsigned integer overflow"))); } return uint16_t (value); } static uint16_t safe_convert_to_uint16 (uint64_t value) { if (value > std::numeric_limits::max ()) { throw tl::Exception (tl::to_string (tr ("Short unsigned integer overflow"))); } return uint16_t (value); } // ------------------------------------------------------------------ // GDS2WriterBase implementation GDS2WriterBase::GDS2WriterBase () : m_dbu (0.0), m_resolve_skew_arrays (false), m_multi_xy (false), m_no_zero_length_paths (false), m_max_vertex_count (0), m_write_cell_properties (false), m_keep_instances (false) { // .. nothing yet .. } void GDS2WriterBase::write_context_string (size_t n, const std::string &s) { // max. size for GDS strings used as payload carrier size_t chunk_size = 32000; short max_short = std::numeric_limits::max (); if (n > size_t (max_short) || s.size () > chunk_size) { // Split strings and use a separate notation: "#,<+p>:..." for the partial // strings. n is the string index and p the part index (zero based). // The property number is not defined in that case. There may be properties with // the same number. See issue #1794. size_t nchunks = (s.size () + (chunk_size - 1)) / chunk_size; while (nchunks > 0) { --nchunks; std::string partial; partial.reserve (chunk_size + 100); // approx. partial += "#"; partial += tl::to_string (n); partial += ","; partial += tl::to_string (nchunks); partial += ":"; size_t pos = nchunks * chunk_size; partial += std::string (s, pos, std::min (s.size (), pos + chunk_size) - pos); write_record_size (6); write_record (sPROPATTR); write_short (n <= size_t (max_short) ? short (n) : max_short); write_string_record (sPROPVALUE, partial); } } else { write_record_size (6); write_record (sPROPATTR); write_short (int16_t (n)); write_string_record (sPROPVALUE, s); } } void GDS2WriterBase::write_context_cell (db::Layout &layout, const short *time_data, const std::vector &cells) { write_record_size (4 + 12 * 2); write_record (sBGNSTR); write_time (time_data); write_time (time_data); write_string_record (sSTRNAME, "$$$CONTEXT_INFO$$$"); std::vector context_prop_strings; if (layout.has_context_info ()) { // Use a dummy BOUNDARY element to attach the global context write_record_size (4); write_record (sBOUNDARY); write_record_size (6); write_record (sLAYER); write_short (0); write_record_size (6); write_record (sDATATYPE); write_short (0); write_record_size (4 + 5 * 2 * 4); write_record (sXY); for (unsigned int i = 0; i < 10; ++i) { write_int (0); } context_prop_strings.clear (); if (layout.get_context_info (context_prop_strings)) { // Hint: write in the reverse order since this way, the reader is more efficient (it knows how many strings // will arrive) for (std::vector ::const_iterator s = context_prop_strings.end (); s != context_prop_strings.begin (); ) { --s; size_t n = std::distance (std::vector ::const_iterator (context_prop_strings.begin ()), s); write_context_string (n, *s); } } write_record_size (4); write_record (sENDEL); } for (std::vector::const_iterator cell = cells.begin (); cell != cells.end (); ++cell) { if (layout.has_context_info (*cell)) { write_record_size (4); write_record (sSREF); write_string_record (sSNAME, m_cell_name_map.cell_name (*cell)); write_record_size (12); write_record (sXY); write_int (0); write_int (0); context_prop_strings.clear (); if (layout.get_context_info (*cell, context_prop_strings)) { // Hint: write in the reverse order since this way, the reader is more efficient (it knows how many strings // will arrive) for (std::vector ::const_iterator s = context_prop_strings.end (); s != context_prop_strings.begin (); ) { --s; size_t n = std::distance (std::vector ::const_iterator (context_prop_strings.begin ()), s); write_context_string (n, *s); } } write_record_size (4); write_record (sENDEL); } } write_record_size (4); write_record (sENDSTR); } void GDS2WriterBase::write_shape (const db::Layout &layout, int layer, int datatype, const db::Shape &shape, double sf) { if (shape.is_text ()) { write_text (layer, datatype, sf, m_dbu, shape, layout, shape.prop_id ()); } else if (shape.is_polygon ()) { write_polygon (layer, datatype, sf, shape, m_multi_xy, m_max_vertex_count, layout, shape.prop_id ()); } else if (shape.is_edge ()) { write_edge (layer, datatype, sf, shape, layout, shape.prop_id ()); } else if (shape.is_edge_pair ()) { write_edge (layer, datatype, sf, shape.edge_pair ().first (), layout, shape.prop_id ()); write_edge (layer, datatype, sf, shape.edge_pair ().second (), layout, shape.prop_id ()); } else if (shape.is_path ()) { if (m_no_zero_length_paths && (shape.path_length () - shape.path_extensions ().first - shape.path_extensions ().second) == 0) { // eliminate the zero-width path db::Polygon poly; shape.polygon (poly); write_polygon (layer, datatype, sf, poly, m_multi_xy, m_max_vertex_count, layout, shape.prop_id (), false); } else { write_path (layer, datatype, sf, shape, m_multi_xy, layout, shape.prop_id ()); } } else if (shape.is_box ()) { write_box (layer, datatype, sf, shape, layout, shape.prop_id ()); } } void GDS2WriterBase::write_cell (db::Layout &layout, const db::Cell &cref, const std::vector > &layers, const std::set &cell_set, double sf, short *time_data) { // cell header write_record_size (4 + 12 * 2); write_record (sBGNSTR); write_time (time_data); write_time (time_data); try { write_string_record (sSTRNAME, m_cell_name_map.cell_name (cref.cell_index ())); } catch (tl::Exception &ex) { throw tl::Exception (ex.msg () + tl::to_string (tr (", writing cell name"))); } // cell body if (m_write_cell_properties && cref.prop_id () != 0) { try { write_properties (layout, cref.prop_id ()); } catch (tl::Exception &ex) { throw tl::Exception (ex.msg () + tl::to_string (tr (", writing layout properties"))); } } // instances for (db::Cell::const_iterator inst = cref.begin (); ! inst.at_end (); ++inst) { // write only instances to selected cells if (m_keep_instances || cell_set.find (inst->cell_index ()) != cell_set.end ()) { progress_checkpoint (); try { write_inst (sf, *inst, true /*normalize*/, m_resolve_skew_arrays, layout, inst->prop_id ()); } catch (tl::Exception &ex) { throw tl::Exception (ex.msg () + tl::to_string (tr (", writing instances"))); } } } // shapes for (std::vector >::const_iterator l = layers.begin (); l != layers.end (); ++l) { if (layout.is_valid_layer (l->first) && l->second.layer >= 0 && l->second.datatype >= 0) { int layer = l->second.layer; if (layer > std::numeric_limits::max ()) { throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write layer numbers larger than %d to GDS2 streams")), int (std::numeric_limits::max ()))); } int datatype = l->second.datatype; if (datatype > std::numeric_limits::max ()) { throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write datatype numbers larger than %d to GDS2 streams")), int (std::numeric_limits::max ()))); } db::ShapeIterator shape (cref.shapes (l->first).begin (db::ShapeIterator::Boxes | db::ShapeIterator::Polygons | db::ShapeIterator::Edges | db::ShapeIterator::EdgePairs | db::ShapeIterator::Paths | db::ShapeIterator::Texts)); while (! shape.at_end ()) { progress_checkpoint (); try { write_shape (layout, layer, datatype, *shape, sf); } catch (tl::Exception &ex) { throw tl::Exception (ex.msg () + tl::sprintf (tl::to_string (tr (", writing layer %d/%d")), layer, datatype)); } ++shape; } } } // end of cell write_record_size (4); write_record (sENDSTR); } void GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::SaveLayoutOptions &options) { set_stream (stream); m_dbu = (options.dbu () == 0.0) ? layout.dbu () : options.dbu (); double sf = options.scale_factor () * (layout.dbu () / m_dbu); if (fabs (sf - 1.0) < 1e-9) { // to avoid rounding problems, set to 1.0 exactly if possible. sf = 1.0; } db::GDS2WriterOptions gds2_options = options.get_options (); layout.add_meta_info ("dbuu", MetaInfo (tl::to_string (tr ("Database unit in user units")), tl::to_string (m_dbu / std::max (1e-9, gds2_options.user_units)))); layout.add_meta_info ("dbum", MetaInfo (tl::to_string (tr ("Database unit in meter")), tl::to_string (m_dbu * 1e-6))); layout.add_meta_info ("libname", MetaInfo (tl::to_string (tr ("Library name")), gds2_options.libname)); std::vector > layers; options.get_valid_layers (layout, layers, db::SaveLayoutOptions::LP_AssignNumber); std::set cell_set; options.get_cells (layout, cell_set, layers); // create a cell index vector sorted bottom-up std::vector cells; cells.reserve (cell_set.size ()); for (db::Layout::bottom_up_const_iterator cell = layout.begin_bottom_up (); cell != layout.end_bottom_up (); ++cell) { if (cell_set.find (*cell) != cell_set.end ()) { cells.push_back (*cell); } } // get current time short time_data [6] = { 0, 0, 0, 0, 0, 0 }; if (gds2_options.write_timestamps) { time_t ti = 0; time (&ti); const struct tm *t = localtime (&ti); if (t) { time_data[0] = t->tm_year + 1900; time_data[1] = t->tm_mon + 1; time_data[2] = t->tm_mday; time_data[3] = t->tm_hour; time_data[4] = t->tm_min; time_data[5] = t->tm_sec; } } std::string str_time = tl::sprintf ("%d/%d/%d %d:%02d:%02d", time_data[1], time_data[2], time_data[0], time_data[3], time_data[4], time_data[5]); layout.add_meta_info ("mod_time", MetaInfo (tl::to_string (tr ("Modification Time")), str_time)); layout.add_meta_info ("access_time", MetaInfo (tl::to_string (tr ("Access Time")), str_time)); m_keep_instances = options.keep_instances (); m_multi_xy = gds2_options.multi_xy_records; m_max_vertex_count = std::max (gds2_options.max_vertex_count, (unsigned int)4); m_no_zero_length_paths = gds2_options.no_zero_length_paths; m_resolve_skew_arrays = gds2_options.resolve_skew_arrays; m_write_cell_properties = gds2_options.write_cell_properties; size_t max_cellname_length = std::max (gds2_options.max_cellname_length, (unsigned int)8); m_cell_name_map = db::WriterCellNameMap (max_cellname_length); m_cell_name_map.replacement ('$'); m_cell_name_map.disallow_all (); // TODO: restrict character set, i.e allow_standard and "$" m_cell_name_map.allow_all_printing (); // For keep instances we need to map all cells since all can be present as instances. // We use top-down assignment to make "upper cells less modified". if (options.keep_instances ()) { for (db::Layout::bottom_up_const_iterator cell = layout.end_bottom_up (); cell != layout.begin_bottom_up (); ) { --cell; m_cell_name_map.insert(*cell, layout.cell_name (*cell)); } } else { for (std::vector::const_iterator cell = cells.end (); cell != cells.begin (); ) { --cell; m_cell_name_map.insert(*cell, layout.cell_name (*cell)); } } // write header write_record_size (6); write_record (sHEADER); write_short (600); write_record_size (4 + 12 * 2); write_record (sBGNLIB); write_time (time_data); write_time (time_data); try { write_string_record (sLIBNAME, gds2_options.libname); } catch (tl::Exception &ex) { throw tl::Exception (ex.msg () + tl::to_string (tr (", writing LIBNAME"))); } write_record_size (4 + 8 * 2); write_record (sUNITS); write_double (m_dbu / std::max (1e-9, gds2_options.user_units)); write_double (m_dbu * 1e-6); // layout properties if (gds2_options.write_file_properties && layout.prop_id () != 0) { try { write_properties (layout, layout.prop_id ()); } catch (tl::Exception &ex) { throw tl::Exception (ex.msg () + tl::to_string (tr (", writing layout properties"))); } } // write context info bool has_context = false; if (options.write_context_info ()) { has_context = layout.has_context_info (); for (std::vector::const_iterator cell = cells.begin (); cell != cells.end () && !has_context; ++cell) { has_context = layout.has_context_info (*cell); } } if (has_context) { try { write_context_cell (layout, time_data, cells); } catch (tl::Exception &ex) { throw tl::Exception (ex.msg () + tl::to_string (tr (", writing context cell"))); } } // body for (std::vector::const_iterator cell = cells.begin (); cell != cells.end (); ++cell) { progress_checkpoint (); const db::Cell &cref (layout.cell (*cell)); // don't write ghost cells unless they are not empty (any more) // also don't write proxy cells which are not employed if ((! cref.is_ghost_cell () || ! cref.empty ()) && (! cref.is_proxy () || ! cref.is_top ())) { try { write_cell (layout, cref, layers, cell_set, sf, time_data); } catch (tl::Exception &ex) { throw tl::Exception (ex.msg () + tl::sprintf (tl::to_string (tr (", writing cell '%s'")), layout.cell_name (*cell))); } } } write_record_size (4); write_record (sENDLIB); progress_checkpoint (); } static bool is_orthogonal (const db::Vector &rv, const db::Vector &cv) { return (rv.x () == 0 && cv.y () == 0) || (rv.y () == 0 && cv.x () == 0); } void GDS2WriterBase::write_inst (double sf, const db::Instance &instance, bool normalize, bool resolve_skew_arrays, const db::Layout &layout, db::properties_id_type prop_id) { db::Vector a, b; unsigned long amax, bmax; bool is_reg = instance.is_regular_array (a, b, amax, bmax); // skew arrays are resolved if required if (is_reg && ! is_orthogonal (a, b) != 0 && resolve_skew_arrays) { is_reg = false; } for (db::CellInstArray::iterator ii = instance.begin (); ! ii.at_end (); ++ii) { db::Trans t = *ii; if (normalize) { // try to normalize orthogonal arrays into "Cadence notation", that is // column and row vectors are positive in the coordinate system of the // rotated array. if (is_reg) { if (amax < 2) { a = db::Vector (); } if (bmax < 2) { b = db::Vector (); } // normalisation only works for orthogonal vectors, parallel to x or y axis, which are not parallel if ((a.x () == 0 || a.y () == 0) && (b.x () == 0 || b.y () == 0) && !((a.x () != 0 && b.x () != 0) || (a.y () != 0 && b.y () != 0))) { db::FTrans fp = db::FTrans(t.rot ()).inverted (); a.transform (fp); b.transform (fp); db::Vector p; for (int i = 0; i < 2; ++i) { db::Vector *q = (i == 0) ? &a : &b; unsigned long n = (i == 0) ? amax : bmax; if (n == 0) { *q = db::Vector (); } else { if (q->x () < 0) { p += db::Vector ((n - 1) * q->x (), 0); q->set_x (-q->x ()); } if (q->y () < 0) { p += db::Vector (0, (n - 1) * q->y ()); q->set_y (-q->y ()); } } } if (a.x () != 0 || b.y () != 0) { std::swap (a, b); std::swap (amax, bmax); } fp = db::FTrans (t.rot ()); a.transform (fp); b.transform (fp); t = t * db::Trans (p); } } } write_record_size (4); write_record (is_reg ? sAREF : sSREF); write_string_record (sSNAME, m_cell_name_map.cell_name (instance.cell_index ())); if (t.rot () != 0 || instance.is_complex ()) { write_record_size (6); write_record (sSTRANS); write_short (t.is_mirror () ? 0x8000 : 0); if (instance.is_complex ()) { db::CellInstArray::complex_trans_type ct = instance.complex_trans (t); write_record_size (4 + 8); write_record (sMAG); write_double (ct.mag ()); write_record_size (4 + 8); write_record (sANGLE); write_double (ct.angle ()); } else { if ((t.rot () % 4) != 0) { write_record_size (4 + 8); write_record (sANGLE); write_double ((t.rot () % 4) * 90.0); } } } if (is_reg) { write_record_size (4 + 2 * 2); write_record (sCOLROW); if (amax > std::numeric_limits::max () || bmax > std::numeric_limits::max ()) { throw tl::Exception (tl::sprintf (tl::to_string (tr ("Cannot write array references with more than %d columns or rows to GDS2 streams")), int (std::numeric_limits::max ()))); } write_short (std::max ((unsigned long) 1, bmax)); write_short (std::max ((unsigned long) 1, amax)); } write_record_size (4 + (is_reg ? 3 : 1) * 2 * 4); write_record (sXY); write_int (scale (sf, t.disp ().x ())); write_int (scale (sf, t.disp ().y ())); if (is_reg) { write_int (scale (sf, t.disp ().x () + b.x () * (long) bmax)); write_int (scale (sf, t.disp ().y () + b.y () * (long) bmax)); write_int (scale (sf, t.disp ().x () + a.x () * (long) amax)); write_int (scale (sf, t.disp ().y () + a.y () * (long) amax)); } finish (layout, prop_id); if (is_reg) { // we have already written all instances break; } } } void GDS2WriterBase::write_box (int layer, int datatype, double sf, const db::Shape &shape, const db::Layout &layout, db::properties_id_type prop_id) { db::Box box (shape.box ()); write_record_size (4); write_record (sBOUNDARY); write_record_size (4 + 2); write_record (sLAYER); write_short (safe_convert_to_uint16 (layer)); write_record_size (4 + 2); write_record (sDATATYPE); write_short (safe_convert_to_uint16 (datatype)); write_record_size (4 + 5 * 2 * 4); write_record (sXY); write_int (scale (sf, box.left ())); write_int (scale (sf, box.bottom ())); write_int (scale (sf, box.left ())); write_int (scale (sf, box.top ())); write_int (scale (sf, box.right ())); write_int (scale (sf, box.top ())); write_int (scale (sf, box.right ())); write_int (scale (sf, box.bottom ())); write_int (scale (sf, box.left ())); write_int (scale (sf, box.bottom ())); finish (layout, prop_id); } void GDS2WriterBase::write_path (int layer, int datatype, double sf, const db::Shape &shape, bool multi_xy, const db::Layout &layout, db::properties_id_type prop_id) { // instantiate the path and draw db::Path path; shape.path (path); write_record_size (4); write_record (sPATH); write_record_size (4 + 2); write_record (sLAYER); write_short (safe_convert_to_uint16 (layer)); write_record_size (4 + 2); write_record (sDATATYPE); write_short (safe_convert_to_uint16 (datatype)); short type = 0; db::Coord w = path.width (); db::Coord se = path.extensions ().first; db::Coord ee = path.extensions ().second; if (se == w / 2 && ee == w / 2) { type = path.round () ? 1 : 2; } else if (se == 0 && ee == 0) { type = 0; } else { type = 4; } write_record_size (4 + 2); write_record (sPATHTYPE); write_short (type); write_record_size (4 + 4); write_record (sWIDTH); write_int (scale (sf, w)); if (type == 4) { write_record_size (4 + 4); write_record (sBGNEXTN); write_int (scale (sf, se)); write_record_size (4 + 4); write_record (sENDEXTN); write_int (scale (sf, ee)); } size_t n = path.points (); db::Path::iterator p = path.begin (); while (n > 0) { // determine number of points to write (all or slice for multi XY mode) size_t nxy = n; if (n > 8100 && multi_xy) { nxy = 8000; } write_record_size (4 + int16_t (nxy) * 2 * 4); write_record (sXY); // write path .. for ( ; p != path.end () && nxy > 0; ++p) { write_int (scale (sf, (*p).x ())); write_int (scale (sf, (*p).y ())); --nxy; --n; } } finish (layout, prop_id); } void GDS2WriterBase::write_edge (int layer, int datatype, double sf, const db::Shape &shape, const db::Layout &layout, db::properties_id_type prop_id) { write_edge (layer, datatype, sf, shape.edge (), layout, prop_id); } void GDS2WriterBase::write_edge (int layer, int datatype, double sf, const db::Edge &e, const db::Layout &layout, db::properties_id_type prop_id) { write_record_size (4); write_record (sPATH); write_record_size (4 + 2); write_record (sLAYER); write_short (safe_convert_to_uint16 (layer)); write_record_size (4 + 2); write_record (sDATATYPE); write_short (safe_convert_to_uint16 (datatype)); write_record_size (4 + 2); write_record (sPATHTYPE); write_short (0); write_record_size (4 + 4); write_record (sWIDTH); write_int (0); write_record_size (4 + 2 * 2 * 4); write_record (sXY); write_int (scale (sf, e.p1 ().x ())); write_int (scale (sf, e.p1 ().y ())); write_int (scale (sf, e.p2 ().x ())); write_int (scale (sf, e.p2 ().y ())); finish (layout, prop_id); } void GDS2WriterBase::write_text (int layer, int datatype, double sf, double dbu, const db::Shape &shape, const db::Layout &layout, db::properties_id_type prop_id) { db::Trans trans = shape.text_trans (); write_record_size (4); write_record (sTEXT); write_record_size (4 + 2); write_record (sLAYER); write_short (safe_convert_to_uint16 (layer)); write_record_size (4 + 2); write_record (sTEXTTYPE); write_short (safe_convert_to_uint16 (datatype)); if (shape.text_halign () != db::NoHAlign || shape.text_valign () != db::NoVAlign || shape.text_font () != db::NoFont) { short ha = short (shape.text_halign () == db::NoHAlign ? db::HAlignLeft : shape.text_halign ()); short va = short (shape.text_valign () == db::NoVAlign ? db::VAlignBottom : shape.text_valign ()); // HINT: currently we don't write the font since the font is not well standardized // short f = (shape.text_font () == db::NoFont ? 0 : (short (shape.text_font ()) & 0xfff)); short f = 0; write_record_size (4 + 2); write_record (sPRESENTATION); write_short (ha + va * 4 + f * 16); } if (trans.rot () != 0 || shape.text_size () != 0) { write_record_size (6); write_record (sSTRANS); write_short (trans.is_mirror () ? 0x8000 : 0); if (shape.text_size () != 0) { write_record_size (4 + 8); write_record (sMAG); write_double (shape.text_size () * sf * dbu); } if ((trans.rot () % 4) != 0) { write_record_size (4 + 8); write_record (sANGLE); write_double ((trans.rot () % 4) * 90.0); } } write_record_size (4 + 2 * 4); write_record (sXY); write_int (scale (sf, trans.disp ().x ())); write_int (scale (sf, trans.disp ().y ())); write_string_record (sSTRING, shape.text_string ()); finish (layout, prop_id); } void GDS2WriterBase::write_polygon (int layer, int datatype, double sf, const db::Polygon &polygon, bool multi_xy, size_t max_vertex, const db::Layout &layout, db::properties_id_type prop_id, bool merged) { bool needs_split = (polygon.vertices () > 4 && polygon.vertices () > max_vertex && !multi_xy); if (polygon.holes () > 0 || (! merged && needs_split)) { // resolve holes or merge polygon as a preparation step for split_polygon which only works properly // on merged polygons ... std::vector polygons; db::EdgeProcessor ep; ep.insert_sequence (polygon.begin_edge ()); db::PolygonContainer pc (polygons); db::PolygonGenerator out (pc, true /*resolve holes*/, needs_split /*min coherence for splitting*/); db::SimpleMerge op; ep.process (out, op); for (std::vector::const_iterator p = polygons.begin (); p != polygons.end (); ++p) { write_polygon (layer, datatype, sf, *p, multi_xy, max_vertex, layout, prop_id, true); } } else if (needs_split) { std::vector polygons; db::split_polygon (polygon, polygons); for (std::vector::const_iterator p = polygons.begin (); p != polygons.end (); ++p) { write_polygon (layer, datatype, sf, *p, multi_xy, max_vertex, layout, prop_id, true /*parts need not to be merged again*/); } } else if (polygon.vertices () > 0) { write_record_size (4); write_record (sBOUNDARY); write_record_size (4 + 2); write_record (sLAYER); write_short (safe_convert_to_uint16 (layer)); write_record_size (4 + 2); write_record (sDATATYPE); write_short (safe_convert_to_uint16 (datatype)); size_t n = polygon.vertices (); db::Polygon::polygon_contour_iterator e = polygon.begin_hull (); while (n > 0) { // determine number of points to write (all or slice for multi XY mode) size_t nxy = n + 1; if (n > 8100 && multi_xy) { nxy = 8000; } write_record_size (4 + int16_t (nxy) * 2 * 4); write_record (sXY); // write polygon .. for ( ; e != polygon.end_hull () && nxy > 0; ++e) { write_int (scale (sf, (*e).x ())); write_int (scale (sf, (*e).y ())); --nxy; --n; } // .. and closing point if (nxy > 0) { e = polygon.begin_hull (); write_int (scale (sf, (*e).x ())); write_int (scale (sf, (*e).y ())); tl_assert (n == 0); } } finish (layout, prop_id); } } void GDS2WriterBase::write_polygon (int layer, int datatype, double sf, const db::Shape &shape, bool multi_xy, size_t max_vertex, const db::Layout &layout, db::properties_id_type prop_id) { if (shape.holes () > 0) { db::Polygon polygon; shape.polygon (polygon); write_polygon (layer, datatype, sf, polygon, multi_xy, max_vertex, layout, prop_id, false); } else { // There is no other way to determine the actual number of points of a generic shape // without instantiating a polygon: size_t n = 0; for (db::Shape::point_iterator e = shape.begin_hull (); e != shape.end_hull (); ++e) { ++n; } if (n > 4 && n > max_vertex && !multi_xy) { // split polygons ... db::Polygon polygon; shape.polygon (polygon); write_polygon (layer, datatype, sf, polygon, multi_xy, max_vertex, layout, prop_id, false); } else if (n > 0) { write_record_size (4); write_record (sBOUNDARY); write_record_size (4 + 2); write_record (sLAYER); write_short (safe_convert_to_uint16 (layer)); write_record_size (4 + 2); write_record (sDATATYPE); write_short (safe_convert_to_uint16 (datatype)); db::Shape::point_iterator e (shape.begin_hull ()); while (n > 0) { // determine number of points to write (all or slice for multi XY mode) size_t nxy = n + 1; if (n > 8100 && multi_xy) { nxy = 8000; } write_record_size (4 + int16_t (nxy) * 2 * 4); write_record (sXY); // write polygon .. for ( ; e != shape.end_hull () && nxy > 0; ++e) { write_int (scale (sf, (*e).x ())); write_int (scale (sf, (*e).y ())); --nxy; --n; } // .. and closing point if (nxy > 0) { e = shape.begin_hull (); write_int (scale (sf, (*e).x ())); write_int (scale (sf, (*e).y ())); tl_assert (n == 0); } } finish (layout, prop_id); } } } void GDS2WriterBase::write_properties (const db::Layout &layout, db::properties_id_type prop_id) { const db::PropertiesRepository::properties_set &props = layout.properties_repository ().properties (prop_id); for (db::PropertiesRepository::properties_set::const_iterator p = props.begin (); p != props.end (); ++p) { const tl::Variant &name = layout.properties_repository ().prop_name (p->first); long attr = -1; if (name.can_convert_to_long ()) { attr = name.to_long (); } if (attr >= 0 && attr <= std::numeric_limits::max ()) { write_record_size (6); write_record (sPROPATTR); write_short ((int16_t) attr); write_string_record (sPROPVALUE, p->second.to_string ()); } } } void GDS2WriterBase::finish (const db::Layout &layout, db::properties_id_type prop_id) { if (prop_id != 0) { write_properties (layout, prop_id); } write_record_size (4); write_record (sENDEL); } void GDS2WriterBase::write_string_record (short record, const std::string &t) { size_t rs = 4 + ((t.size () + 1) / 2) * 2; if (rs > std::numeric_limits::max ()) { throw tl::Exception (tl::to_string (tr ("String max. length overflow"))); } write_record_size (uint16_t (rs)); write_record (record); write_string (t); } } // namespace db