diff --git a/src/plugins/streamers/cif/db_plugin/dbCIFWriter.cc b/src/plugins/streamers/cif/db_plugin/dbCIFWriter.cc index a4e84a83a..7af28cd01 100644 --- a/src/plugins/streamers/cif/db_plugin/dbCIFWriter.cc +++ b/src/plugins/streamers/cif/db_plugin/dbCIFWriter.cc @@ -61,11 +61,7 @@ CIFWriter::operator<<(const std::string &s) CIFWriter & CIFWriter::operator<<(endl_tag) { -#ifdef _WIN32 - *this << "\r\n"; -#else *this << "\n"; -#endif return *this; } @@ -78,6 +74,8 @@ CIFWriter::xy_sep () const void CIFWriter::write (db::Layout &layout, tl::OutputStream &stream, const db::SaveLayoutOptions &options) { + stream.set_as_text (true); + m_options = options.get_options (); mp_stream = &stream; diff --git a/src/plugins/streamers/magic/db_plugin/dbMAGReader.cc b/src/plugins/streamers/magic/db_plugin/dbMAGReader.cc index 6cba1ed41..b1e6fe3a2 100644 --- a/src/plugins/streamers/magic/db_plugin/dbMAGReader.cc +++ b/src/plugins/streamers/magic/db_plugin/dbMAGReader.cc @@ -431,7 +431,7 @@ MAGReader::read_rlabel (tl::Extractor &ex, Layout &layout, cell_index_type cell_ text.move (db::DVector (x, y)); - if (lname != "space") { // really? "space"? + if (true || lname != "space") { // @@@ really? "space"? ignore it? std::pair ll = open_layer (layout, lname); if (ll.first) { layout.cell (cell_index).shapes (ll.second).insert ((text * m_lambda).transformed (m_dbu_trans_inv)); diff --git a/src/plugins/streamers/magic/db_plugin/dbMAGWriter.cc b/src/plugins/streamers/magic/db_plugin/dbMAGWriter.cc index c149aeff6..91de27ccb 100644 --- a/src/plugins/streamers/magic/db_plugin/dbMAGWriter.cc +++ b/src/plugins/streamers/magic/db_plugin/dbMAGWriter.cc @@ -23,8 +23,11 @@ #include "dbMAGWriter.h" #include "dbPolygonGenerators.h" +#include "dbPolygonTools.h" #include "tlStream.h" #include "tlUtils.h" +#include "tlFileUtils.h" +#include "tlUri.h" #include #include @@ -41,6 +44,7 @@ MAGWriter::MAGWriter () { m_progress.set_format (tl::to_string (tr ("%.0f MB"))); m_progress.set_unit (1024 * 1024); + m_timestamp = 0; } void @@ -49,368 +53,224 @@ MAGWriter::write (db::Layout &layout, tl::OutputStream &stream, const db::SaveLa m_options = options.get_options (); mp_stream = &stream; -#if 0 // @@@ - // compute the scale factor to get to the 10 nm basic database unit of MAG - double tl_scale = options.scale_factor () * layout.dbu () / 0.01; + m_base_uri = tl::URI (stream.path ()); + m_ext = tl::extension (m_base_uri.path ()); + m_base_uri.set_path (tl::dirname (m_base_uri.path ())); - std::vector > layers; - options.get_valid_layers (layout, layers, db::SaveLayoutOptions::LP_AssignName); + m_cells_written.clear (); + m_cells_to_write.clear (); + m_layer_names.clear (); + m_timestamp = 0; // @@@ set timestamp? - std::set cell_set; - options.get_cells (layout, cell_set, layers); + if (layout.end_top_cells () - layout.begin_top_down () == 1) { - // create a cell index vector sorted bottom-up - std::vector cells; - cells.reserve (cell_set.size ()); + // write the one top cell to the given stream. Otherwise + write_cell (*layout.begin_top_down (), layout, stream); - 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); + } else { + + stream << "# KLayout is not writing this file as there are multiple top cells - see those files for the individual cells."; + + for (db::Layout::top_down_const_iterator c = layout.begin_top_down (); c != layout.end_top_cells (); ++c) { + m_cells_to_write.insert (std::make_pair (*c, filename_for_cell (*c, layout))); } + } - time_t t = time(NULL); - struct tm tt = *localtime(&t); + while (! m_cells_to_write.empty ()) { - char timestr[100]; - strftime(timestr, sizeof (timestr), "%F %T", &tt); + std::map cells_to_write; + cells_to_write.swap (m_cells_to_write); - // Write header - *this << "(MAG file written " << (const char *)timestr << " by KLayout);" << endl; - - // TODO: this can be done more intelligently .. - int tl_scale_divider; - int tl_scale_denom; - for (tl_scale_divider = 1; tl_scale_divider < 1000; ++tl_scale_divider) { - tl_scale_denom = int (floor (0.5 + tl_scale * tl_scale_divider)); - if (fabs (tl_scale_denom - tl_scale * tl_scale_divider) < 1e-6) { - break; + for (std::map::const_iterator cw = cells_to_write.begin (); cw != cells_to_write.end (); ++cw) { + tl::OutputStream os (cw->second, tl::OutputStream::OM_Auto, true); + write_cell (cw->first, layout, os); } + + } +} + +std::string +MAGWriter::layer_name (unsigned int li, const Layout &layout) +{ + // @@@ TODO: avoid built-in names, like "end", "space", "labels" ... + + std::map::const_iterator i = m_layer_names.find (li); + if (i != m_layer_names.end ()) { + return i->second; } - int cell_index = 0; - std::map db_to_cif_index_map; - std::set called_cells; + if (m_layer_names.empty ()) { - // body - for (std::vector::const_iterator cell = cells.begin (); cell != cells.end (); ++cell) { - - m_progress.set (mp_stream->pos ()); - - // cell body - const db::Cell &cref (layout.cell (*cell)); - - ++cell_index; - db_to_cif_index_map.insert (std::make_pair (*cell, cell_index)); - - double sf = 1.0; - - *this << "DS " << cell_index << " " << tl_scale_denom << " " << tl_scale_divider << ";" << endl; - *this << "9 " << tl::to_word_or_quoted_string (layout.cell_name (*cell)) << ";" << endl; - - // instances - for (db::Cell::const_iterator inst = cref.begin (); ! inst.at_end (); ++inst) { - - // write only instances to selected cells - if (cell_set.find (inst->cell_index ()) != cell_set.end ()) { - - called_cells.insert (inst->cell_index ()); - - m_progress.set (mp_stream->pos ()); - - std::map::const_iterator cif_index = db_to_cif_index_map.find (inst->cell_index ()); - tl_assert(cif_index != db_to_cif_index_map.end ()); - - // resolve instance arrays - for (db::Cell::cell_inst_array_type::iterator pp = inst->begin (); ! pp.at_end (); ++pp) { - - *this << "C" << cif_index->second; - - // convert the transformation into MAG's notation - - db::CplxTrans t (inst->complex_trans (*pp)); - db::Vector d (t.disp() * sf); - - if (t.is_mirror()) { - *this << " MY"; - } - - double a = t.angle(); - while (a < 0) { - a += 360.0; - } - double ya = 0.0, xa = 0.0; - if (a < 45 || a > 315) { - xa = 1.0; - ya = tan(a / 180.0 * M_PI); - } else if (a < 135) { - xa = 1.0 / tan(a / 180.0 * M_PI); - ya = 1.0; - } else if (a < 225) { - xa = -1.0; - ya = tan(a / 180.0 * M_PI); - } else { - xa = 1.0 / tan(a / 180.0 * M_PI); - ya = -1.0; - } - - // TODO: that can be done smarter ... - while (fabs (xa - floor (0.5 + xa)) > 1e-3 || fabs (ya - floor (0.5 + ya)) > 1e-3) { - xa *= 2.0; - ya *= 2.0; - } - - *this << " R" << floor (0.5 + xa) << xy_sep () << floor (0.5 + ya); - - *this << " T" << d.x() << xy_sep () << d.y(); - - *this << ";" << endl; + for (db::Layout::layer_iterator i = layout.begin_layers (); i != layout.end_layers (); ++i) { + db::LayerProperties lp = layout.get_properties ((*i).first); + if (lp.is_named ()) { + m_layer_names.insert (std::make_pair ((*i).first, lp.name)); + } + } + for (db::Layout::layer_iterator i = layout.begin_layers (); i != layout.end_layers (); ++i) { + db::LayerProperties lp = layout.get_properties ((*i).first); + if (! lp.is_named ()) { + // @@@ TODO: avoid duplicates + std::string ld_name = std::string ("L") + tl::to_string (lp.layer); + if (lp.datatype) { + ld_name += "D"; + ld_name += tl::to_string (lp.datatype); } - + m_layer_names.insert (std::make_pair ((*i).first, ld_name)); } - - } - - // shapes - for (std::vector >::const_iterator l = layers.begin (); l != layers.end (); ++l) { - - m_needs_emit = true; - m_layer = l->second; - - write_texts (layout, cref, l->first, sf); - write_polygons (layout, cref, l->first, sf); - write_paths (layout, cref, l->first, sf); - write_boxes (layout, cref, l->first, sf); - - m_progress.set (mp_stream->pos ()); - - } - - // end of cell - *this << "DF;" << endl; - - } - - if (m_options.dummy_calls) { - - // If requested, write dummy calls for all top cells - for (std::vector::const_iterator cell = cells.begin (); cell != cells.end (); ++cell) { - - if (called_cells.find (*cell) == called_cells.end ()) { - - std::map::const_iterator cif_index = db_to_cif_index_map.find (*cell); - tl_assert(cif_index != db_to_cif_index_map.end ()); - *this << "C" << cif_index->second << ";" << endl; - - } - } } - // end of file - *this << "E" << endl; -#endif - - m_progress.set (mp_stream->pos ()); + return m_layer_names [li]; } -#if 0 // @@@ -void -MAGWriter::emit_layer() +std::string +MAGWriter::filename_for_cell (db::cell_index_type ci, db::Layout &layout) { - if (m_needs_emit) { - m_needs_emit = false; - *this << "L " << tl::to_word_or_quoted_string(m_layer.name, "0123456789_.$") << ";" << endl; + tl::URI uri (m_base_uri); + if (uri.path ().empty ()) { + uri.set_path (std::string (layout.cell_name (ci)) + m_ext); } + return uri.to_string (); } -void -MAGWriter::write_texts (const db::Layout &layout, const db::Cell &cell, unsigned int layer, double sf) +void +MAGWriter::write_cell (db::cell_index_type ci, db::Layout &layout, tl::OutputStream &os) { - db::ShapeIterator shape (cell.shapes (layer).begin (db::ShapeIterator::Texts)); - while (! shape.at_end ()) { + os.set_as_text (true); + os << "magic\n"; - m_progress.set (mp_stream->pos ()); + // @@@ write tech - emit_layer (); + os << "timestamp " << m_timestamp << "\n"; - *this << "94 " << tl::to_word_or_quoted_string(shape->text_string(), "0123456789:<>/&%$!.-_#+*?\\[]{}"); + db::Cell &cell = layout.cell (ci); - double h = shape->text_size () * layout.dbu (); + for (db::Layout::layer_iterator i = layout.begin_layers (); i != layout.end_layers (); ++i) { - db::Vector p (shape->text_trans ().disp () * sf); - *this << " " << p.x() << xy_sep () << p.y () << " " << h << ";" << endl; - - ++shape; - - } -} - -void -MAGWriter::write_polygons (const db::Layout & /*layout*/, const db::Cell &cell, unsigned int layer, double sf) -{ - db::ShapeIterator shape (cell.shapes (layer).begin (db::ShapeIterator::Polygons)); - while (! shape.at_end ()) { - - m_progress.set (mp_stream->pos ()); - - db::Polygon poly; - shape->polygon (poly); - - if (poly.holes () > 0) { - - // 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 (poly.begin_edge ()); - db::PolygonContainer pc (polygons); - db::PolygonGenerator out (pc, true /*resolve holes*/, false /*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 (*p, sf); - } - - } else { - write_polygon (poly, sf); - } - - ++shape; - - } -} - -void -MAGWriter::write_polygon (const db::Polygon &polygon, double sf) -{ - emit_layer (); - *this << "P"; - for (db::Polygon::polygon_contour_iterator p = polygon.begin_hull (); p != polygon.end_hull (); ++p) { - db::Point pp (*p * sf); - *this << " " << pp.x () << xy_sep () << pp.y (); - } - *this << ";" << endl; -} - -void -MAGWriter::write_boxes (const db::Layout & /*layout*/, const db::Cell &cell, unsigned int layer, double sf) -{ - db::ShapeIterator shape (cell.shapes (layer).begin (db::ShapeIterator::Boxes)); - while (! shape.at_end ()) { - - m_progress.set (mp_stream->pos ()); - - emit_layer (); - - db::Box b (shape->bbox () * sf); - *this << "B " << b.width () << " " << b.height () << " " << b.center ().x () << xy_sep () << b.center ().y () << ";" << endl; - - ++shape; - - } -} - -void -MAGWriter::write_paths (const db::Layout & /*layout*/, const db::Cell &cell, unsigned int layer, double sf) -{ - db::ShapeIterator shape (cell.shapes (layer).begin (db::ShapeIterator::Paths)); - while (! shape.at_end ()) { - - m_progress.set (mp_stream->pos ()); - -#if 0 - - // "official" code: write only round paths as such - other paths are converted to polygons - if (shape->round_path ()) { - - emit_layer (); - - *this << "W " << long (floor (0.5 + sf * shape->path_width ())); - - for (db::Shape::point_iterator p = shape->begin_point (); p != shape->end_point (); ++p) { - db::Point pp (*p * sf); - *this << " " << pp.x () << xy_sep () << pp.y (); - } - - *this << ";" << endl; - - } else { - db::Polygon poly; - shape->polygon (poly); - write_polygon (poly, sf); - } - -#else - - // Use 98 extension for path type. Only use polygons for custom extensions. - int path_type = -1; - if (shape->round_path ()) { - if (shape->path_extensions ().first == shape->path_width () / 2 && shape->path_extensions ().second == shape->path_width () / 2) { - path_type = 1; - } - } else { - if (shape->path_extensions ().first == 0 && shape->path_extensions ().second == 0) { - path_type = 0; - } else if (shape->path_extensions ().first == shape->path_width () / 2 && shape->path_extensions ().second == shape->path_width () / 2) { - path_type = 2; + unsigned int li = (*i).first; + if (! cell.shapes (li).empty ()) { + os << "<< " << tl::to_word_or_quoted_string (layer_name (li, layout)) << " >>\n"; + for (db::Shapes::shape_iterator s = cell.shapes (li).begin (db::ShapeIterator::Boxes | db::ShapeIterator::Polygons | db::ShapeIterator::Paths); ! s.at_end (); ++s) { + write_polygon (s->polygon (), layout, os); } } - size_t npts = 0; - for (db::Shape::point_iterator p = shape->begin_point (); p != shape->end_point () && npts < 2; ++p) { - ++npts; - } + } - if (npts == 0) { + bool any = false; - // ignore paths with zero points - - } else if (path_type == 1 && npts == 1) { - - // produce a round flash for single-point round paths - - emit_layer (); - - *this << "R " << long (floor (0.5 + sf * shape->path_width ())); - - db::Point pp (*shape->begin_point () * sf); - *this << " " << pp.x () << xy_sep () << pp.y (); - - *this << ";" << endl; - - } else if (path_type >= 0 && npts > 1) { - - emit_layer (); - - *this << "98 " << path_type << ";" << endl; - - *this << "W " << long (floor (0.5 + sf * shape->path_width ())); - - for (db::Shape::point_iterator p = shape->begin_point (); p != shape->end_point (); ++p) { - db::Point pp (*p * sf); - *this << " " << pp.x () << xy_sep () << pp.y (); + for (db::Layout::layer_iterator i = layout.begin_layers (); i != layout.end_layers (); ++i) { + for (db::Shapes::shape_iterator s = cell.shapes ((*i).first).begin (db::ShapeIterator::Texts); ! s.at_end (); ++s) { + if (! any) { + os << "<< labels >>\n"; } - - *this << ";" << endl; - - } else { - db::Polygon poly; - shape->polygon (poly); - write_polygon (poly, sf); + write_label (layer_name ((*i).first, layout), s->text (), layout, os); } + } -#endif - - ++shape; - + m_cell_id.clear (); + for (db::Cell::const_iterator i = cell.begin (); ! i.at_end (); ++i) { + if (m_cells_written.find (i->cell_index ()) == m_cells_written.end ()) { + m_cells_written.insert (i->cell_index ()); + m_cells_to_write.insert (std::make_pair (i->cell_index (), filename_for_cell (i->cell_index (), layout))); + } + write_instance (i->cell_inst (), layout, os); + } +} + +namespace { + + class TrapezoidWriter + : public SimplePolygonSink + { + public: + TrapezoidWriter (tl::OutputStream &os, double scale) + : mp_os (&os), m_scale (scale) + { } + + virtual void put (const db::SimplePolygon &polygon) + { + if (! polygon.is_box ()) { + // @@@ TODO: handle non-boxes + } + + db::DBox b = db::DBox (polygon.box ()) * m_scale; + // @@@ TODO: floating coords on output? + (*mp_os) << "rect " << b.left () << " " << b.bottom () << " " << b.right () << " " << b.top () << "\n"; + } + + private: + tl::OutputStream *mp_os; + double m_scale; + }; +} + +void +MAGWriter::write_polygon (const db::Polygon &poly, const db::Layout &layout, tl::OutputStream &os) +{ + TrapezoidWriter writer (os, layout.dbu () / m_options.lambda); + db::decompose_trapezoids (poly, TD_simple, writer); +} + +void +MAGWriter::write_label (const std::string &layer, const db::Text &text, const db::Layout &layout, tl::OutputStream &os) +{ + db::DVector v = db::DVector (text.trans ().disp ()) * (layout.dbu () / m_options.lambda); + + std::string s = text.string (); + if (s.find ("\n") != std::string::npos) { + s = tl::replaced (s, "\n", "\\n"); + } + + os << "rlabel " << tl::to_word_or_quoted_string (layer) << " " << v.x () << " " << v.y () << " " << v.x () << " " << v.y () << " 0 " << s << "\n"; +} + +void +MAGWriter::write_instance (const db::CellInstArray &inst, const db::Layout &layout, tl::OutputStream &os) +{ + double sf = layout.dbu () / m_options.lambda; + + int id = (m_cell_id [inst.object ().cell_index ()] += 1); + std::string cn = layout.cell_name (inst.object ().cell_index ()); + os << "use " << tl::to_word_or_quoted_string (cn) << " " << tl::to_word_or_quoted_string (cn + "_" + tl::to_string (id)); + + os << "timestamp " << m_timestamp << "\n"; + + db::ICplxTrans tr = inst.complex_trans (); + db::Matrix2d m = tr.to_matrix2d (); + + db::DVector d = db::DVector (tr.disp ()) * sf; + os << "transform " << m.m11 () << " " << m.m12 () << " " << d.x () << " " << m.m21 () << " " << m.m22 () << " " << d.y () << "\n"; + + { + db::Vector a, b; + unsigned long na = 0, nb = 0; + if (inst.is_regular_array (a, b, na, nb) && ((a.x () == 0 && b.y () == 0) || (a.y () == 0 && b.x () == 0))) { + + na = std::max ((unsigned long) 1, na); + nb = std::max ((unsigned long) 1, nb); + + if (a.y () != 0) { + std::swap (a, b); + std::swap (na, nb); + } + + db::DVector da = db::DVector (a) * sf; + db::DVector db = db::DVector (b) * sf; + os << "array " << 0 << " " << (na - 1) << " " << da.x () << " " << 0 << " " << (nb - 1) << " " << db.y () << "\n"; + + } + } + + { + db::DBox b = db::DBox (inst.bbox (db::box_convert ())) * sf; + os << "box " << b.left () << " " << b.bottom () << " " << b.right () << " " << b.top () << "\n"; } } -#endif } diff --git a/src/plugins/streamers/magic/db_plugin/dbMAGWriter.h b/src/plugins/streamers/magic/db_plugin/dbMAGWriter.h index d648d9e1c..c831a06aa 100644 --- a/src/plugins/streamers/magic/db_plugin/dbMAGWriter.h +++ b/src/plugins/streamers/magic/db_plugin/dbMAGWriter.h @@ -31,6 +31,7 @@ #include "dbMAGFormat.h" #include "dbSaveLayoutOptions.h" #include "tlProgress.h" +#include "tlUri.h" namespace tl { @@ -61,11 +62,23 @@ public: void write (db::Layout &layout, tl::OutputStream &stream, const db::SaveLayoutOptions &options); private: - struct endl_tag { }; - tl::OutputStream *mp_stream; MAGWriterOptions m_options; tl::AbsoluteProgress m_progress; + std::set m_cells_written; + std::map m_cells_to_write; + tl::URI m_base_uri; + std::string m_ext; + std::map m_layer_names; + size_t m_timestamp; + std::map m_cell_id; + + std::string filename_for_cell (db::cell_index_type ci, db::Layout &layout); + void write_cell (db::cell_index_type ci, db::Layout &layout, tl::OutputStream &os); + std::string layer_name (unsigned int li, const db::Layout &layout); + void write_polygon (const db::Polygon &poly, const db::Layout &layout, tl::OutputStream &os); + void write_label (const std::string &layer, const db::Text &text, const Layout &layout, tl::OutputStream &os); + void write_instance (const db::CellInstArray &inst, const db::Layout &layout, tl::OutputStream &os); }; } // namespace db diff --git a/src/plugins/streamers/magic/unit_tests/dbMAGReader.cc b/src/plugins/streamers/magic/unit_tests/dbMAGReader.cc index 6766055b5..012de3800 100644 --- a/src/plugins/streamers/magic/unit_tests/dbMAGReader.cc +++ b/src/plugins/streamers/magic/unit_tests/dbMAGReader.cc @@ -29,7 +29,7 @@ #include -static void run_test (tl::TestBase *_this, const std::string &base, const char *file, const char *file_au, const char *map = 0, double dbu = 0.001, bool dummy_calls = false, bool blank_sep = false) +static void run_test (tl::TestBase *_this, const std::string &base, const char *file, const char *file_au, const char *map = 0, double dbu = 0.001) { db::MAGReaderOptions *opt = new db::MAGReaderOptions(); opt->dbu = dbu; @@ -137,6 +137,7 @@ static void run_test (tl::TestBase *_this, const std::string &base, const char * } } +#if 0 // @@@ TEST(1a) { run_test (_this, tl::testsrc_private (), "t1.cif.gz", "t1a_au.gds.gz"); @@ -193,3 +194,4 @@ TEST(rot_boxes) { run_test (_this, tl::testsrc (), "issue_305.cif", "issue_305_au.gds"); } +#endif // @@@ diff --git a/src/tl/tl/tlStream.cc b/src/tl/tl/tlStream.cc index 409c8cd29..69594712f 100644 --- a/src/tl/tl/tlStream.cc +++ b/src/tl/tl/tlStream.cc @@ -691,7 +691,7 @@ OutputStreamBase *create_file_stream (const std::string &path, OutputStream::Out } OutputStream::OutputStream (const std::string &abstract_path, OutputStreamMode om, bool as_text) - : m_pos (0), mp_delegate (0), m_owns_delegate (false), m_as_text (as_text) + : m_pos (0), mp_delegate (0), m_owns_delegate (false), m_as_text (as_text), m_path (abstract_path) { // Determine output mode om = output_mode_from_filename (abstract_path, om); @@ -728,6 +728,12 @@ OutputStream::~OutputStream () } } +void +OutputStream::set_as_text (bool f) +{ + m_as_text = f; +} + inline void fast_copy (char *t, const char *s, size_t n) { if (n >= sizeof (unsigned long)) { diff --git a/src/tl/tl/tlStream.h b/src/tl/tl/tlStream.h index 75127728b..19c854690 100644 --- a/src/tl/tl/tlStream.h +++ b/src/tl/tl/tlStream.h @@ -1132,6 +1132,19 @@ public: */ void flush (); + /** + * @brief Gets the path that was specified in the constructor + */ + const std::string &path () const + { + return m_path; + } + + /** + * @brief Configures the stream for text output + */ + void set_as_text (bool f); + protected: void reset_pos () { @@ -1145,6 +1158,7 @@ private: bool m_as_text; char *mp_buffer; size_t m_buffer_capacity, m_buffer_pos; + std::string m_path; void put_raw (const char *b, size_t n);