diff --git a/src/db/db/dbAsIfFlatEdgePairs.cc b/src/db/db/dbAsIfFlatEdgePairs.cc index d1fb9b0cd..d0f098e27 100644 --- a/src/db/db/dbAsIfFlatEdgePairs.cc +++ b/src/db/db/dbAsIfFlatEdgePairs.cc @@ -152,10 +152,6 @@ AsIfFlatEdgePairs::processed (const EdgePairProcessorBase &filter) const { std::unique_ptr edge_pairs (new FlatEdgePairs ()); - if (filter.result_must_not_be_merged ()) { - edge_pairs->set_merged_semantics (false); - } - std::vector res_edge_pairs; for (EdgePairsIterator e = begin (); ! e.at_end (); ++e) { diff --git a/src/db/db/dbAsIfFlatTexts.cc b/src/db/db/dbAsIfFlatTexts.cc index 0e38590a6..8c73f1c1a 100644 --- a/src/db/db/dbAsIfFlatTexts.cc +++ b/src/db/db/dbAsIfFlatTexts.cc @@ -164,6 +164,24 @@ AsIfFlatTexts::filtered (const TextFilterBase &filter) const return new_texts.release (); } +TextsDelegate * +AsIfFlatTexts::processed (const TextProcessorBase &filter) const +{ + std::unique_ptr texts (new FlatTexts ()); + + std::vector res_texts; + + for (TextsIterator e = begin (); ! e.at_end (); ++e) { + res_texts.clear (); + filter.process (*e, res_texts); + for (std::vector::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 { diff --git a/src/db/db/dbAsIfFlatTexts.h b/src/db/db/dbAsIfFlatTexts.h index ffff931e1..07c023741 100644 --- a/src/db/db/dbAsIfFlatTexts.h +++ b/src/db/db/dbAsIfFlatTexts.h @@ -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) diff --git a/src/db/db/dbDeepTexts.cc b/src/db/db/dbDeepTexts.cc index f328452ea..ebf6daccd 100644 --- a/src/db/db/dbDeepTexts.cc +++ b/src/db/db/dbDeepTexts.cc @@ -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 (deep_layer (), filter); +} + RegionDelegate * DeepTexts::processed_to_polygons (const TextToPolygonProcessorBase &filter) const { diff --git a/src/db/db/dbDeepTexts.h b/src/db/db/dbDeepTexts.h index 1bd5bb66c..44c367e07 100644 --- a/src/db/db/dbDeepTexts.h +++ b/src/db/db/dbDeepTexts.h @@ -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); diff --git a/src/db/db/dbEmptyTexts.h b/src/db/db/dbEmptyTexts.h index c224dadaa..9bd206de9 100644 --- a/src/db/db/dbEmptyTexts.h +++ b/src/db/db/dbEmptyTexts.h @@ -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; diff --git a/src/db/db/dbTexts.cc b/src/db/db/dbTexts.cc index 31d6ae0a8..0b90c8897 100644 --- a/src/db/db/dbTexts.cc +++ b/src/db/db/dbTexts.cc @@ -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)); diff --git a/src/db/db/dbTexts.h b/src/db/db/dbTexts.h index 3ed3cf1f8..82d017187 100644 --- a/src/db/db/dbTexts.h +++ b/src/db/db/dbTexts.h @@ -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. diff --git a/src/db/db/dbTextsDelegate.h b/src/db/db/dbTextsDelegate.h index 6a818cec5..05f26188e 100644 --- a/src/db/db/dbTextsDelegate.h +++ b/src/db/db/dbTextsDelegate.h @@ -40,6 +40,7 @@ class RegionDelegate; class EdgesDelegate; class Layout; +typedef shape_collection_processor TextProcessorBase; typedef shape_collection_processor TextToPolygonProcessorBase; typedef db::generic_shape_iterator_delegate_base 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; diff --git a/src/db/db/gsiDeclDbEdgePairs.cc b/src/db/db/gsiDeclDbEdgePairs.cc index 6f4b1d60f..e5926b466 100644 --- a/src/db/db/gsiDeclDbEdgePairs.cc +++ b/src/db/db/gsiDeclDbEdgePairs.cc @@ -120,7 +120,7 @@ Class > 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 " diff --git a/src/db/db/gsiDeclDbText.cc b/src/db/db/gsiDeclDbText.cc index a823cecc8..be37cf36e 100644 --- a/src/db/db/gsiDeclDbText.cc +++ b/src/db/db/gsiDeclDbText.cc @@ -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))); } diff --git a/src/db/db/gsiDeclDbTexts.cc b/src/db/db/gsiDeclDbTexts.cc index d186c2c32..885508c41 100644 --- a/src/db/db/gsiDeclDbTexts.cc +++ b/src/db/db/gsiDeclDbTexts.cc @@ -107,6 +107,78 @@ Class decl_TextFilterImpl ("db", "TextFilter", "This class has been introduced in version 0.29.\n" ); +// --------------------------------------------------------------------------------- +// TextProcessor binding + +Class > decl_TextProcessor ("db", "TextOperator", + shape_processor_impl::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 > decl_TextToPolygonProcessor ("db", "TextToPolygonOperator", + shape_processor_impl::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 *f) +{ + return r->processed (*f); +} + +static void process_tt (db::Texts *r, const shape_processor_impl *f) +{ + r->process (*f); +} + +static db::Region processed_tp (const db::Texts *r, const shape_processor_impl *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 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" diff --git a/testdata/ruby/dbTextsTest.rb b/testdata/ruby/dbTextsTest.rb index 385c50a00..70429a1a0 100644 --- a/testdata/ruby/dbTextsTest.rb +++ b/testdata/ruby/dbTextsTest.rb @@ -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