Implemented solution for issue #2145 (default_text_size option for GDS2 writer)

This commit is contained in:
Matthias Koefferlein 2025-09-08 23:23:05 +02:00
parent 71929bf2be
commit 712237e67b
12 changed files with 258 additions and 58 deletions

View File

@ -50,6 +50,8 @@ GenericWriterOptions::GenericWriterOptions ()
m_gds2_write_timestamps = save_options.get_option_by_name ("gds2_write_timestamps").to_bool ();
m_gds2_write_cell_properties = save_options.get_option_by_name ("gds2_write_cell_properties").to_bool ();
m_gds2_write_file_properties = save_options.get_option_by_name ("gds2_write_file_properties").to_bool ();
tl::Variant def_text_size = save_options.get_option_by_name ("gds2_default_text_size");
m_gds2_default_text_size = def_text_size.is_nil () ? -1.0 : def_text_size.to_double ();
m_oasis_compression_level = save_options.get_option_by_name ("oasis_compression_level").to_int ();
m_oasis_write_cblocks = save_options.get_option_by_name ("oasis_write_cblocks").to_bool ();
@ -201,6 +203,13 @@ GenericWriterOptions::add_options (tl::CommandLineOptions &cmd, const std::strin
"This option enables a GDS2 extension that allows writing of file properties to GDS2 files. "
"Consumers that don't support this feature, may not be able to read such a GDS2 files."
)
<< tl::arg (group +
"#--default-text-size", &m_gds2_default_text_size, "Default text size",
"This text size (given in micrometers) is applied to text objects not coming with their "
"own text size (technically: with a zero text size). It can be set to 0 to preserve an original "
"text size of zero. This option is also handy to give text objects from OASIS files a "
"specific size. By default, text objects without a size (i.e. with a zero size) do not receive one."
)
;
}
@ -379,6 +388,7 @@ GenericWriterOptions::configure (db::SaveLayoutOptions &save_options, const db::
save_options.set_option_by_name ("gds2_write_timestamps", m_gds2_write_timestamps);
save_options.set_option_by_name ("gds2_write_cell_properties", m_gds2_write_cell_properties);
save_options.set_option_by_name ("gds2_write_file_properties", m_gds2_write_file_properties);
save_options.set_option_by_name ("gds2_default_text_size", m_gds2_default_text_size < 0.0 ? tl::Variant () : tl::Variant (m_gds2_default_text_size));
save_options.set_option_by_name ("oasis_compression_level", m_oasis_compression_level);
save_options.set_option_by_name ("oasis_write_cblocks", m_oasis_write_cblocks);

View File

@ -123,6 +123,7 @@ private:
bool m_gds2_write_timestamps;
bool m_gds2_write_cell_properties;
bool m_gds2_write_file_properties;
double m_gds2_default_text_size;
int m_oasis_compression_level;
bool m_oasis_write_cblocks;

View File

@ -82,9 +82,10 @@ TEST(1)
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_multi_xy_records").to_bool (), false);
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_write_timestamps").to_bool (), true);
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_no_zero_length_paths").to_bool (), false);
EXPECT_EQ (tl::to_string (stream_opt.get_option_by_name ("gds2_user_units").to_double ()), "1");
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_user_units").to_double (), 1.0);
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_write_cell_properties").to_bool (), false);
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_write_file_properties").to_bool (), false);
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_default_text_size").to_string (), "nil");
EXPECT_EQ (stream_opt.get_option_by_name ("oasis_write_cblocks").to_bool (), true);
EXPECT_EQ (stream_opt.get_option_by_name ("oasis_compression_level").to_int (), 2);
EXPECT_EQ (stream_opt.get_option_by_name ("oasis_strict_mode").to_bool (), true);
@ -107,9 +108,10 @@ TEST(1)
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_multi_xy_records").to_bool (), true);
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_write_timestamps").to_bool (), false);
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_no_zero_length_paths").to_bool (), true);
EXPECT_EQ (tl::to_string (stream_opt.get_option_by_name ("gds2_user_units").to_double ()), "2.5");
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_user_units").to_double (), 2.5);
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_write_cell_properties").to_bool (), true);
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_write_file_properties").to_bool (), true);
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_default_text_size").to_string (), "nil");
EXPECT_EQ (stream_opt.get_option_by_name ("oasis_write_cblocks").to_bool (), false);
EXPECT_EQ (stream_opt.get_option_by_name ("oasis_compression_level").to_int (), 9);
EXPECT_EQ (stream_opt.get_option_by_name ("oasis_strict_mode").to_bool (), false);
@ -118,6 +120,52 @@ TEST(1)
EXPECT_EQ (stream_opt.get_option_by_name ("oasis_write_std_properties_ext").to_int (), 2);
}
// Testing writer options (default_text_size)
TEST(2)
{
bd::GenericWriterOptions opt;
tl::CommandLineOptions cmd;
opt.add_options (cmd);
const char *argv[] = {
"x",
"--default-text-size=1.25",
};
cmd.parse (sizeof (argv) / sizeof (argv[0]), const_cast<char **> (argv));
db::Layout layout;
db::SaveLayoutOptions stream_opt;
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_default_text_size").to_string (), "nil");
opt.configure (stream_opt, layout);
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_default_text_size").to_string (), "1.25");
}
// Testing writer options (default_text_size)
TEST(3)
{
bd::GenericWriterOptions opt;
tl::CommandLineOptions cmd;
opt.add_options (cmd);
const char *argv[] = {
"x",
"--default-text-size=-1",
};
cmd.parse (sizeof (argv) / sizeof (argv[0]), const_cast<char **> (argv));
db::Layout layout;
db::SaveLayoutOptions stream_opt;
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_default_text_size").to_string (), "nil");
opt.configure (stream_opt, layout);
EXPECT_EQ (stream_opt.get_option_by_name ("gds2_default_text_size").to_string (), "nil");
}
static std::string cells2string (const db::Layout &layout, const std::set<db::cell_index_type> &cells)
{
std::string res;
@ -131,7 +179,7 @@ static std::string cells2string (const db::Layout &layout, const std::set<db::ce
}
// Testing writer options: cell resolution
TEST(2)
TEST(4)
{
// Build a layout with the hierarchy
// TOP -> A, B

View File

@ -73,6 +73,7 @@ class GDS2FormatDeclaration
tl::make_member (&db::GDS2WriterOptions::write_cell_properties, "write-cell-properties") +
tl::make_member (&db::GDS2WriterOptions::write_file_properties, "write-file-properties") +
tl::make_member (&db::GDS2WriterOptions::no_zero_length_paths, "no-zero-length-paths") +
tl::make_member (&db::GDS2WriterOptions::default_text_size, "default-text-size") +
tl::make_member (&db::GDS2WriterOptions::multi_xy_records, "multi-xy-records") +
tl::make_member (&db::GDS2WriterOptions::resolve_skew_arrays, "resolve-skew-arrays") +
tl::make_member (&db::GDS2WriterOptions::max_vertex_count, "max-vertex-count") +

View File

@ -115,7 +115,8 @@ public:
user_units (1.0),
write_timestamps (true),
write_cell_properties (false),
write_file_properties (false)
write_file_properties (false),
default_text_size (-1.0)
{
// .. nothing yet ..
}
@ -191,6 +192,14 @@ public:
*/
bool write_file_properties;
/**
* @brief The default text size if none is given (in fact, if the text size is zero)
*
* You can set to option to 0 to preserve the zero text size on writing.
* A negative value means the text size is not set if missing.
*/
double default_text_size;
/**
* @brief Implementation of FormatSpecificWriterOptions
*/

View File

@ -155,7 +155,7 @@ static uint16_t safe_convert_to_uint16 (uint64_t value)
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)
m_max_vertex_count (0), m_write_cell_properties (false), m_keep_instances (false), m_default_text_size (-1.0)
{
// .. nothing yet ..
}
@ -481,6 +481,7 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S
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;
m_default_text_size = gds2_options.default_text_size;
size_t max_cellname_length = std::max (gds2_options.max_cellname_length, (unsigned int)8);
@ -902,7 +903,7 @@ GDS2WriterBase::write_text (int layer, int datatype, double sf, double dbu, cons
write_short (ha + va * 4 + f * 16);
}
if (trans.rot () != 0 || shape.text_size () != 0) {
if (trans.rot () != 0 || shape.text_size () != 0 || m_default_text_size >= 0.0) {
write_record_size (6);
write_record (sSTRANS);
@ -912,6 +913,10 @@ GDS2WriterBase::write_text (int layer, int datatype, double sf, double dbu, cons
write_record_size (4 + 8);
write_record (sMAG);
write_double (shape.text_size () * sf * dbu);
} else if (m_default_text_size >= 0.0) {
write_record_size (4 + 8);
write_record (sMAG);
write_double (m_default_text_size * sf);
}
if ((trans.rot () % 4) != 0) {

View File

@ -174,6 +174,7 @@ private:
size_t m_max_vertex_count;
bool m_write_cell_properties;
bool m_keep_instances;
double m_default_text_size;
void write_properties (const db::Layout &layout, db::properties_id_type prop_id);
void write_context_cell (db::Layout &layout, const short *time_data, const std::vector<cell_index_type> &cells);

View File

@ -115,6 +115,17 @@ static bool get_gds2_write_timestamps (const db::SaveLayoutOptions *options)
return options->get_options<db::GDS2WriterOptions> ().write_timestamps;
}
static void set_gds2_default_text_size (db::SaveLayoutOptions *options, const tl::Variant &v)
{
options->get_options<db::GDS2WriterOptions> ().default_text_size = v.is_nil () ? -1.0 : v.to_double ();
}
static tl::Variant get_gds2_default_text_size (const db::SaveLayoutOptions *options)
{
double ts = options->get_options<db::GDS2WriterOptions> ().default_text_size;
return ts < 0.0 ? tl::Variant () : tl::Variant (ts);
}
static void set_gds2_libname (db::SaveLayoutOptions *options, const std::string &n)
{
options->get_options<db::GDS2WriterOptions> ().libname = n;
@ -189,6 +200,22 @@ gsi::ClassExt<db::SaveLayoutOptions> gds2_writer_options (
"@brief Gets a value indicating whether the current time is written into the GDS2 timestamp fields\n"
"\nThis property has been added in version 0.21.16.\n"
) +
gsi::method_ext ("gds2_default_text_size=", &set_gds2_default_text_size, gsi::arg ("size"),
"@brief Specifies the default text size to use when a text does not have a size\n"
"\n"
"Text object can have no size, e.g. when they are read from OASIS files. Technically such texts\n"
"are represented by text object with a zero size. You can configure the GDS writer to use a specific\n"
"text size in this case. This property specifies the default text size in micrometer units. This\n"
"size can be set to 0 to preserve a zero size in GDS files read.\n"
"\n"
"Set this attribute to nil to disable writing of a text size if none is specified.\n"
"\n"
"\nThis property has been added in version 0.30.4.\n"
) +
gsi::method_ext ("gds2_default_text_size", &get_gds2_default_text_size,
"@brief Gets the default text size to use when a text does not have a size\n"
"\nThis property has been added in version 0.30.4.\n"
) +
gsi::method_ext ("gds2_no_zero_length_paths=", &set_gds2_no_zero_length_paths, gsi::arg ("flag"),
"@brief Eliminates zero-length paths if true\n"
"\n"

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>656</width>
<height>315</height>
<width>673</width>
<height>360</height>
</rect>
</property>
<property name="windowTitle">
@ -50,12 +50,8 @@
<property name="spacing">
<number>6</number>
</property>
<item row="7" column="0" colspan="3">
<widget class="QCheckBox" name="write_timestamps">
<property name="text">
<string>Write current time to time stamps (BGNLIB, BGNSTR)</string>
</property>
</widget>
<item row="0" column="1" colspan="2">
<widget class="QLineEdit" name="libname_le"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
@ -64,6 +60,16 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="cell_name_length_le"/>
</item>
<item row="7" column="0" colspan="3">
<widget class="QCheckBox" name="no_zero_length_paths">
<property name="text">
<string>Eliminate zero-length paths (convert to BOUNDARY)</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_4">
<property name="text">
@ -71,10 +77,53 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="cell_name_length_le"/>
<item row="6" column="0" colspan="3">
<widget class="QCheckBox" name="resolve_skew_arrays_cbx">
<property name="text">
<string>Resolve skew (non-orthogonal) arrays into single instances</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="3">
<item row="8" column="0" colspan="3">
<widget class="QCheckBox" name="write_timestamps">
<property name="text">
<string>Write current time to time stamps (BGNLIB, BGNSTR)</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Library name</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="3">
<widget class="QCheckBox" name="multi_xy_cbx">
<property name="text">
<string>Multi-XY record mode for boundaries
(enables infinitely large polygons/paths at the cost of compatibility)</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>(keep empty for unspecified limit)</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Max. vertices</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="max_vertex_le"/>
</item>
<item row="5" column="0" colspan="3">
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
@ -139,52 +188,20 @@
</layout>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="max_vertex_le"/>
<item row="4" column="1">
<widget class="QLineEdit" name="default_text_size_le"/>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_3">
<item row="4" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>(keep empty for unspecified limit)</string>
<string>Default text size</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="3">
<widget class="QCheckBox" name="multi_xy_cbx">
<item row="4" column="2">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Multi-XY record mode for boundaries
(enables infinitely large polygons/paths at the cost of compatibility)</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Library name</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Max. vertices</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QLineEdit" name="libname_le"/>
</item>
<item row="6" column="0" colspan="3">
<widget class="QCheckBox" name="no_zero_length_paths">
<property name="text">
<string>Eliminate zero-length paths (convert to BOUNDARY)</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="3">
<widget class="QCheckBox" name="resolve_skew_arrays_cbx">
<property name="text">
<string>Resolve skew (non-orthogonal) arrays into single instances</string>
<string>µm (empty for no size)</string>
</property>
</widget>
</item>

View File

@ -65,6 +65,7 @@ GDS2WriterOptionPage::setup (const db::FormatSpecificWriterOptions *o, const db:
mp_ui->max_vertex_le->setEnabled (! options->multi_xy_records);
mp_ui->max_vertex_le->setText (tl::to_qstring (tl::to_string (options->max_vertex_count)));
mp_ui->cell_name_length_le->setText (tl::to_qstring (tl::to_string (options->max_cellname_length)));
mp_ui->default_text_size_le->setText (tl::to_qstring (options->default_text_size >= 0.0 ? tl::to_string (options->default_text_size) : std::string ()));
mp_ui->libname_le->setText (tl::to_qstring (tl::to_string (options->libname)));
}
}
@ -83,6 +84,19 @@ GDS2WriterOptionPage::commit (db::FormatSpecificWriterOptions *o, const db::Tech
options->write_file_properties = mp_ui->write_file_properties->isChecked ();
options->no_zero_length_paths = mp_ui->no_zero_length_paths->isChecked ();
std::string ts_str = tl::to_string (mp_ui->default_text_size_le->text ());
{
double ts = -1;
tl::Extractor ex (ts_str.c_str ());
if (! ex.at_end ()) {
tl::from_string_ext (ts_str, ts);
if (ts < 0.0) {
throw tl::Exception (tl::to_string (QObject::tr ("Default text size cannot be negative")));
}
}
options->default_text_size = ts;
}
tl::from_string_ext (tl::to_string (mp_ui->max_vertex_le->text ()), n);
if (! options->multi_xy_records) {
if (n > 8191) {

View File

@ -1509,6 +1509,66 @@ TEST(143)
EXPECT_EQ (run_test_with_error (23.0, layout), "Scaling failed: coordinate underflow, writing layer 1/0, writing cell 'TOP'");
}
void run_text_size_test (tl::TestBase *_this, db::Coord size, double def_size, db::Coord exp_size)
{
db::Layout layout;
db::cell_index_type top_index = layout.add_cell ("TOP");
db::Cell &top = layout.cell (top_index);
unsigned int l1 = layout.insert_layer (db::LayerProperties (1, 0));
db::Text text ("TEXT", db::Trans ());
text.size (size);
top.shapes (l1).insert (text);
db::GDS2WriterOptions opt;
opt.default_text_size = def_size;
std::string tmp_file = _this->tmp_file ("tmp.gds");
{
tl::OutputStream stream (tmp_file);
db::SaveLayoutOptions options;
options.set_format ("GDS2");
options.set_options (new db::GDS2WriterOptions (opt));
db::Writer writer (options);
writer.write (layout, stream);
}
db::Layout layout_read;
{
tl::InputStream file (tmp_file);
db::Reader reader (file);
reader.read (layout_read);
}
l1 = layout_read.get_layer (db::LayerProperties (1, 0));
const db::Cell &top_read = layout_read.cell (*layout_read.begin_top_down ());
db::Shape text_read = *top_read.shapes (l1).begin (db::ShapeIterator::All);
EXPECT_EQ (text_read.text_string (), "TEXT");
EXPECT_EQ (text_read.text_size (), exp_size);
}
// default text size
TEST(144a)
{
run_text_size_test (_this, 0, 1.25, 1250);
}
TEST(144b)
{
run_text_size_test (_this, 500, 1.25, 500);
}
TEST(144c)
{
run_text_size_test (_this, 0, 0.0, 0);
}
TEST(144d)
{
run_text_size_test (_this, 0, -1.0, 0);
}
// Extreme fracturing by max. points
TEST(166)
{
@ -1518,4 +1578,3 @@ TEST(166)
}

View File

@ -113,6 +113,14 @@ class SaveLayoutOptions_TestClass < TestBase
opt.gds2_write_file_properties = false
assert_equal(opt.gds2_write_file_properties?, false)
assert_equal(opt.gds2_default_text_size.inspect, "nil")
opt.gds2_default_text_size = nil
assert_equal(opt.gds2_default_text_size.inspect, "nil")
opt.gds2_default_text_size = -1.0
assert_equal(opt.gds2_default_text_size.inspect, "nil")
opt.gds2_default_text_size = 1.0
assert_equal(opt.gds2_default_text_size, 1.0)
opt.gds2_write_timestamps = true
assert_equal(opt.gds2_write_timestamps?, true)
opt.gds2_write_timestamps = false