More processors and tests

This commit is contained in:
Matthias Koefferlein 2024-01-28 15:57:01 +01:00
parent ce88affa67
commit 8d6125dd74
13 changed files with 258 additions and 15 deletions

View File

@ -152,10 +152,6 @@ AsIfFlatEdgePairs::processed (const EdgePairProcessorBase &filter) const
{
std::unique_ptr<FlatEdgePairs> edge_pairs (new FlatEdgePairs ());
if (filter.result_must_not_be_merged ()) {
edge_pairs->set_merged_semantics (false);
}
std::vector<db::EdgePair> res_edge_pairs;
for (EdgePairsIterator e = begin (); ! e.at_end (); ++e) {

View File

@ -164,6 +164,24 @@ AsIfFlatTexts::filtered (const TextFilterBase &filter) const
return new_texts.release ();
}
TextsDelegate *
AsIfFlatTexts::processed (const TextProcessorBase &filter) const
{
std::unique_ptr<FlatTexts> texts (new FlatTexts ());
std::vector<db::Text> res_texts;
for (TextsIterator e = begin (); ! e.at_end (); ++e) {
res_texts.clear ();
filter.process (*e, res_texts);
for (std::vector<db::Text>::const_iterator er = res_texts.begin (); er != res_texts.end (); ++er) {
texts->insert (*er);
}
}
return texts.release ();
}
RegionDelegate *
AsIfFlatTexts::processed_to_polygons (const TextToPolygonProcessorBase &filter) const
{

View File

@ -55,6 +55,12 @@ public:
virtual TextsDelegate *filtered (const TextFilterBase &) const;
virtual TextsDelegate *process_in_place (const TextProcessorBase &proc)
{
return processed (proc);
}
virtual TextsDelegate *processed (const TextProcessorBase &proc) const;
virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &filter) const;
virtual TextsDelegate *add_in_place (const Texts &other)

View File

@ -474,6 +474,18 @@ DeepTexts *DeepTexts::apply_filter (const TextFilterBase &filter) const
return res.release ();
}
TextsDelegate *DeepTexts::process_in_place (const TextProcessorBase &filter)
{
// TODO: implement to be really in-place
return processed (filter);
}
TextsDelegate *
DeepTexts::processed (const TextProcessorBase &filter) const
{
return shape_collection_processed_impl<db::Text, db::Text, db::DeepTexts> (deep_layer (), filter);
}
RegionDelegate *
DeepTexts::processed_to_polygons (const TextToPolygonProcessorBase &filter) const
{

View File

@ -80,6 +80,8 @@ public:
virtual TextsDelegate *filter_in_place (const TextFilterBase &filter);
virtual TextsDelegate *filtered (const TextFilterBase &) const;
virtual TextsDelegate *process_in_place (const TextProcessorBase &);
virtual TextsDelegate *processed (const TextProcessorBase &) const;
virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &filter) const;
virtual TextsDelegate *add_in_place (const Texts &other);

View File

@ -57,6 +57,8 @@ public:
virtual TextsDelegate *filter_in_place (const TextFilterBase &) { return this; }
virtual TextsDelegate *filtered (const TextFilterBase &) const { return new EmptyTexts (); }
virtual TextsDelegate *process_in_place (const TextProcessorBase &) { return this; }
virtual TextsDelegate *processed (const TextProcessorBase &) const { return new EmptyTexts (); }
virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &) const;
virtual RegionDelegate *polygons (db::Coord e) const;

View File

@ -199,6 +199,11 @@ MutableTexts *Texts::mutable_texts ()
return texts;
}
Texts Texts::processed (const TextProcessorBase &proc) const
{
return Texts (mp_delegate->processed (proc));
}
void Texts::processed (Region &output, const TextToPolygonProcessorBase &filter) const
{
output = Region (mp_delegate->processed_to_polygons (filter));

View File

@ -316,7 +316,25 @@ public:
}
/**
* @brief Processes the edges into polygons
* @brief Processes the edge pairs in-place
*
* This method will run the processor over all texts and replace the collection by the results.
*/
Texts &process (const TextProcessorBase &proc)
{
set_delegate (mp_delegate->process_in_place (proc));
return *this;
}
/**
* @brief Processes the texts
*
* This method will run the processor over all texts and return a new text collection with the results.
*/
Texts processed (const TextProcessorBase &proc) const;
/**
* @brief Processes the texts into polygons
*
* This method will run the processor over all edges and return a region
* with the outputs of the processor.

View File

@ -40,6 +40,7 @@ class RegionDelegate;
class EdgesDelegate;
class Layout;
typedef shape_collection_processor<db::Text, db::Text> TextProcessorBase;
typedef shape_collection_processor<db::Text, db::Polygon> TextToPolygonProcessorBase;
typedef db::generic_shape_iterator_delegate_base <db::Text> TextsIteratorDelegate;
@ -94,7 +95,9 @@ public:
virtual TextsDelegate *filter_in_place (const TextFilterBase &filter) = 0;
virtual TextsDelegate *filtered (const TextFilterBase &filter) const = 0;
virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &filter) const = 0;
virtual TextsDelegate *process_in_place (const TextProcessorBase &proc) = 0;
virtual TextsDelegate *processed (const TextProcessorBase &proc) const = 0;
virtual RegionDelegate *processed_to_polygons (const TextToPolygonProcessorBase &proc) const = 0;
virtual RegionDelegate *polygons (db::Coord e) const = 0;
virtual EdgesDelegate *edges () const = 0;

View File

@ -120,7 +120,7 @@ Class<shape_processor_impl<db::EdgePairProcessorBase> > decl_EdgePairProcessor (
"operator class and pass an instance to the \\EdgePairs#processed or \\EdgePairs#process method.\n"
"\n"
"Conceptually, these methods take each edge pair from the edge pair collection and present it to the operator's 'process' method.\n"
"The result of this call is a list of zero to many output edge_pairs derived from the input edge pair.\n"
"The result of this call is a list of zero to many output edge pairs derived from the input edge pair.\n"
"The output edge pair collection is the sum over all these individual results.\n"
"\n"
"The magic happens when deep mode edge pair collections are involved. In that case, the processor will use as few calls as possible "

View File

@ -98,17 +98,17 @@ struct text_defs
t->font (db::Font (f));
}
static int get_font (C *t)
static int get_font (const C *t)
{
return t->font ();
}
static point_type get_pos (C *t)
static point_type get_pos (const C *t)
{
return t->trans () * point_type ();
}
static box_type get_bbox (C *t)
static box_type get_bbox (const C *t)
{
point_type p = get_pos (t);
return box_type (p, p);
@ -124,7 +124,7 @@ struct text_defs
t->halign (db::HAlign (f));
}
static db::HAlign get_halign (C *t)
static db::HAlign get_halign (const C *t)
{
return t->halign ();
}
@ -139,12 +139,12 @@ struct text_defs
t->valign (db::VAlign (f));
}
static db::VAlign get_valign (C *t)
static db::VAlign get_valign (const C *t)
{
return t->valign ();
}
static C moved (C *c, const vector_type &p)
static C moved (const C *c, const vector_type &p)
{
return c->transformed (simple_trans_type (p));
}
@ -155,7 +155,7 @@ struct text_defs
return *c;
}
static C moved_xy (C *c, coord_type dx, coord_type dy)
static C moved_xy (const C *c, coord_type dx, coord_type dy)
{
return c->transformed (simple_trans_type (vector_type (dx, dy)));
}

View File

@ -107,6 +107,78 @@ Class<gsi::TextFilterImpl> decl_TextFilterImpl ("db", "TextFilter",
"This class has been introduced in version 0.29.\n"
);
// ---------------------------------------------------------------------------------
// TextProcessor binding
Class<shape_processor_impl<db::TextProcessorBase> > decl_TextProcessor ("db", "TextOperator",
shape_processor_impl<db::TextProcessorBase>::method_decls (false),
"@brief A generic text operator\n"
"\n"
"Text processors are an efficient way to process texts from an text collection. To apply a processor, derive your own "
"operator class and pass an instance to the \\Texts#processed or \\Texts#process method.\n"
"\n"
"Conceptually, these methods take each text from the edge pair collection and present it to the operator's 'process' method.\n"
"The result of this call is a list of zero to many output texts derived from the input text.\n"
"The output text collection is the sum over all these individual results.\n"
"\n"
"The magic happens when deep mode text collections are involved. In that case, the processor will use as few calls as possible "
"and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You "
"need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
"before using it.\n"
"\n"
"You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant "
"formation which is not always desired and blows up the hierarchy.\n"
"\n"
"Here is some example that replaces the text string:"
"\n"
"@code\n"
"class ReplaceTextString < RBA::TextOperator\n"
"\n"
" # Constructor\n"
" def initialize\n"
" self.is_isotropic_and_scale_invariant # orientation and scale do not matter\n"
" end\n"
" \n"
" # Replaces the string by a number representing the string length\n"
" def process(text)\n"
" new_text = text.dup # need a copy as we cannot modify the text passed\n"
" new_text.string = text.string.size.to_s\n"
" return [ new_text ]\n"
" end\n"
"\n"
"end\n"
"\n"
"texts = ... # some Texts object\n"
"modified = texts.processed(ReplaceTextString::new)\n"
"@/code\n"
"\n"
"This class has been introduced in version 0.29.\n"
);
Class<shape_processor_impl<db::TextToPolygonProcessorBase> > decl_TextToPolygonProcessor ("db", "TextToPolygonOperator",
shape_processor_impl<db::TextToPolygonProcessorBase>::method_decls (false),
"@brief A generic text-to-polygon operator\n"
"\n"
"Text processors are an efficient way to process texts from an text collection. To apply a processor, derive your own "
"operator class and pass an instance to the \\Texts#processed method.\n"
"\n"
"Conceptually, these methods take each text from the text collection and present it to the operator's 'process' method.\n"
"The result of this call is a list of zero to many output polygons derived from the input text.\n"
"The output region is the sum over all these individual results.\n"
"\n"
"The magic happens when deep mode text collections are involved. In that case, the processor will use as few calls as possible "
"and exploit the hierarchical compression if possible. It needs to know however, how the operator behaves. You "
"need to configure the operator by calling \\is_isotropic, \\is_scale_invariant or \\is_isotropic_and_scale_invariant "
"before using it.\n"
"\n"
"You can skip this step, but the processor algorithm will assume the worst case then. This usually leads to cell variant "
"formation which is not always desired and blows up the hierarchy.\n"
"\n"
"For a basic example see the \\TextOperator class, with the exception that this incarnation delivers polygons.\n"
"\n"
"This class has been introduced in version 0.29.\n"
);
// ---------------------------------------------------------------------------------
// Texts binding
@ -239,6 +311,23 @@ static void filter (db::Texts *r, const TextFilterImpl *f)
r->filter (*f);
}
static db::Texts processed_tt (const db::Texts *r, const shape_processor_impl<db::TextProcessorBase> *f)
{
return r->processed (*f);
}
static void process_tt (db::Texts *r, const shape_processor_impl<db::TextProcessorBase> *f)
{
r->process (*f);
}
static db::Region processed_tp (const db::Texts *r, const shape_processor_impl<db::TextToPolygonProcessorBase> *f)
{
db::Region out;
r->processed (out, *f);
return out;
}
static db::Texts with_text (const db::Texts *r, const std::string &text, bool inverse)
{
db::TextStringFilter f (text, inverse);
@ -500,6 +589,24 @@ Class<db::Texts> decl_Texts (decl_dbShapeCollection, "db", "Texts",
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("process", &process_tt, gsi::arg ("process"),
"@brief Applies a generic text processor in place (replacing the texts from the text collection)\n"
"See \\TextProcessor for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("processed", &processed_tt, gsi::arg ("processed"),
"@brief Applies a generic text processor and returns a processed copy\n"
"See \\TextProcessor for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("processed", &processed_tp, gsi::arg ("processed"),
"@brief Applies a generic text-to-polygon processor and returns a region with the results\n"
"See \\TextToPolygonProcessor for a description of this feature.\n"
"\n"
"This method has been introduced in version 0.29.\n"
) +
method_ext ("with_text", with_text, gsi::arg ("text"), gsi::arg ("inverse"),
"@brief Filter the text by text string\n"
"If \"inverse\" is false, this method returns the texts with the given string.\n"

View File

@ -29,7 +29,7 @@ def csort(s)
# splits at ");(" without consuming the brackets
s.split(/(?<=\));(?=\()/).sort.join(";")
end
class TextStringLengthFilter < RBA::TextFilter
# Constructor
@ -45,6 +45,37 @@ class TextStringLengthFilter < RBA::TextFilter
end
class ReplaceTextString < RBA::TextOperator
# Constructor
def initialize
self.is_isotropic_and_scale_invariant # orientation and scale do not matter
end
# Replaces the string by a number representing the string length
def process(text)
new_text = text.dup # need a copy as we cannot modify the text passed
new_text.string = text.string.size.to_s
return [ new_text ]
end
end
class SomeTextToPolygonOperator < RBA::TextToPolygonOperator
# Constructor
def initialize
self.is_isotropic_and_scale_invariant # orientation and scale do not matter
end
# Replaces the string by a number representing the string length
def process(text)
s = text.string.size * 10
return [ RBA::Polygon::new(text.bbox.enlarged(s)) ]
end
end
class DBTexts_TestClass < TestBase
# Basics
@ -369,6 +400,49 @@ class DBTexts_TestClass < TestBase
end
# Generic processors
def test_generic_processors_tt
# Some basic tests for the processor class
f = ReplaceTextString::new
assert_equal(f.wants_variants?, true)
f.wants_variants = false
assert_equal(f.wants_variants?, false)
# Smoke test
f.is_isotropic
f.is_scale_invariant
# Some application
texts = RBA::Texts::new
texts.insert(RBA::Text::new("abc", RBA::Trans::new))
texts.insert(RBA::Text::new("a long text", RBA::Trans::M45))
assert_equal(texts.processed(ReplaceTextString::new).to_s, "('3',r0 0,0);('11',m45 0,0)")
assert_equal(texts.to_s, "('abc',r0 0,0);('a long text',m45 0,0)")
texts.process(ReplaceTextString::new)
assert_equal(texts.to_s, "('3',r0 0,0);('11',m45 0,0)")
end
# Generic processors
def test_generic_processors_tp
p = SomeTextToPolygonOperator::new
texts = RBA::Texts::new
texts.insert(RBA::Text::new("abc", RBA::Trans::new))
texts.insert(RBA::Text::new("a long text", RBA::Trans::M45))
assert_equal(texts.processed(p).to_s, "(-30,-30;-30,30;30,30;30,-30);(-110,-110;-110,110;110,110;110,-110)")
assert_equal(texts.to_s, "('abc',r0 0,0);('a long text',m45 0,0)")
end
end