|
| 1 | + |
| 2 | +/* |
| 3 | +
|
| 4 | + KLayout Layout Viewer |
| 5 | + Copyright (C) 2006-2025 Matthias Koefferlein |
| 6 | +
|
| 7 | + This program is free software; you can redistribute it and/or modify |
| 8 | + it under the terms of the GNU General Public License as published by |
| 9 | + the Free Software Foundation; either version 2 of the License, or |
| 10 | + (at your option) any later version. |
| 11 | +
|
| 12 | + This program is distributed in the hope that it will be useful, |
| 13 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 | + GNU General Public License for more details. |
| 16 | +
|
| 17 | + You should have received a copy of the GNU General Public License |
| 18 | + along with this program; if not, write to the Free Software |
| 19 | + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| 20 | +
|
| 21 | +*/ |
| 22 | + |
| 23 | + |
| 24 | +#include "gsiDecl.h" |
| 25 | +#include "layTextInfo.h" |
| 26 | +#include "layLayoutViewBase.h" |
| 27 | +#include "layEditorUtils.h" |
| 28 | +#include "layMarker.h" |
| 29 | +#include "dbShape.h" |
| 30 | + |
| 31 | +namespace gsi |
| 32 | +{ |
| 33 | + |
| 34 | +class TextInfo |
| 35 | +{ |
| 36 | +public: |
| 37 | + TextInfo (lay::LayoutViewBase *view) |
| 38 | + : mp_view (view), m_textinfo (view), m_tv (view) |
| 39 | + { |
| 40 | + m_border = lay::marker_text_border_in_pixels (); |
| 41 | + } |
| 42 | + |
| 43 | + void set_border (double pixels) |
| 44 | + { |
| 45 | + m_border = pixels; |
| 46 | + } |
| 47 | + |
| 48 | + double get_border () const |
| 49 | + { |
| 50 | + return m_border; |
| 51 | + } |
| 52 | + |
| 53 | + db::Box bbox_from_shape (const db::Shape &shape) |
| 54 | + { |
| 55 | + if (! mp_view || ! shape.is_text ()) { |
| 56 | + return db::Box (); |
| 57 | + } |
| 58 | + |
| 59 | + const db::Shapes *shapes = shape.shapes (); |
| 60 | + if (! shapes) { |
| 61 | + return db::Box (); |
| 62 | + } |
| 63 | + |
| 64 | + const db::Cell *cell = shapes->cell (); |
| 65 | + if (! cell) { |
| 66 | + return db::Box (); |
| 67 | + } |
| 68 | + |
| 69 | + const db::Layout *layout = cell->layout (); |
| 70 | + if (! layout) { |
| 71 | + return db::Box (); |
| 72 | + } |
| 73 | + |
| 74 | + int layer_index = -1; |
| 75 | + for (auto l = layout->begin_layers (); l != layout->end_layers () && layer_index < 0; ++l) { |
| 76 | + if (&cell->shapes ((*l).first) == shapes) { |
| 77 | + layer_index = int ((*l).first); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + if (layer_index < 0) { |
| 82 | + return db::Box (); |
| 83 | + } |
| 84 | + |
| 85 | + int cv_index = -1; |
| 86 | + for (unsigned int i = 0; i < mp_view->cellviews () && cv_index < 0; ++i) { |
| 87 | + const lay::CellView &cv = mp_view->cellview (i); |
| 88 | + if (cv.is_valid () && &cv->layout () == layout) { |
| 89 | + cv_index = int (i); |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + if (cv_index < 0) { |
| 94 | + return db::Box (); |
| 95 | + } |
| 96 | + |
| 97 | + db::Text t; |
| 98 | + shape.text (t); |
| 99 | + return bbox_from_text (t, (unsigned int) cv_index, (unsigned int) layer_index); |
| 100 | + } |
| 101 | + |
| 102 | + db::Box bbox_from_text (const db::Text &text, unsigned int cv_index, int layer) |
| 103 | + { |
| 104 | + if (! mp_view) { |
| 105 | + return db::Box (); |
| 106 | + } |
| 107 | + |
| 108 | + const lay::CellView &cv = mp_view->cellview (cv_index); |
| 109 | + if (! cv.is_valid ()) { |
| 110 | + return db::Box (); |
| 111 | + } |
| 112 | + |
| 113 | + db::CplxTrans dbu_trans (cv->layout ().dbu ()); |
| 114 | + return dbu_trans.inverted () * bbox_from_dtext (dbu_trans * text, cv_index, layer); |
| 115 | + } |
| 116 | + |
| 117 | + db::DBox bbox_from_dtext (const db::DText &dtext, int cv_index, int layer) |
| 118 | + { |
| 119 | + if (! mp_view) { |
| 120 | + return db::DBox (); |
| 121 | + } |
| 122 | + |
| 123 | + db::DCplxTrans tv_trans, ctx_trans; |
| 124 | + |
| 125 | + if (cv_index >= 0 && layer >= 0) { |
| 126 | + |
| 127 | + const lay::CellView &cv = mp_view->cellview (cv_index); |
| 128 | + if (cv.is_valid () && cv->layout ().is_valid_layer (layer)) { |
| 129 | + |
| 130 | + db::CplxTrans dbu_trans (cv->layout ().dbu ()); |
| 131 | + ctx_trans = dbu_trans * cv.context_trans () * dbu_trans.inverted (); |
| 132 | + |
| 133 | + const std::vector<db::DCplxTrans> *tv_list = m_tv.per_cv_and_layer (cv_index, layer); |
| 134 | + if (tv_list != 0 && ! tv_list->empty ()) { |
| 135 | + tv_trans = tv_list->front (); |
| 136 | + } |
| 137 | + |
| 138 | + } |
| 139 | + |
| 140 | + } |
| 141 | + |
| 142 | + db::DCplxTrans vp_trans = mp_view->viewport ().trans () * tv_trans; |
| 143 | + db::DBox box = m_textinfo.bbox (ctx_trans * dtext, vp_trans); |
| 144 | + |
| 145 | + double b = m_border / vp_trans.mag (); |
| 146 | + return box.enlarged (db::DVector (b, b)); |
| 147 | + } |
| 148 | + |
| 149 | +private: |
| 150 | + tl::weak_ptr<lay::LayoutViewBase> mp_view; |
| 151 | + lay::TextInfo m_textinfo; |
| 152 | + lay::TransformationVariants m_tv; |
| 153 | + double m_border; |
| 154 | +}; |
| 155 | + |
| 156 | +gsi::TextInfo *new_textinfo (lay::LayoutViewBase *view) |
| 157 | +{ |
| 158 | + return new gsi::TextInfo (view); |
| 159 | +} |
| 160 | + |
| 161 | +Class<gsi::TextInfo> decl_TextInfo ("lay", "TextInfo", |
| 162 | + gsi::constructor ("new", &new_textinfo, gsi::arg ("view"), |
| 163 | + "@brief Creates a TextInfo object for a given layout view\n" |
| 164 | + ) + |
| 165 | + gsi::method ("border=", &gsi::TextInfo::set_border, gsi::arg ("pixels"), |
| 166 | + "@brief Sets the border in pixels\n" |
| 167 | + "This attribute adds a border between the bounding box edges and the character polygons " |
| 168 | + "for better readability of the text when a box is drawn around them. The value is given in " |
| 169 | + "screen pixels. The default value is the one used for the markers in the application." |
| 170 | + ) + |
| 171 | + gsi::method ("border", &gsi::TextInfo::get_border, |
| 172 | + "@brief Gets the border in pixels\n" |
| 173 | + "See \\border= for details about this attribute." |
| 174 | + ) + |
| 175 | + gsi::method ("bbox", &gsi::TextInfo::bbox_from_shape, gsi::arg ("shape"), |
| 176 | + "@brief Obtains the bounding box for the given text-type shape\n" |
| 177 | + "\n" |
| 178 | + "If the shape is not a text object or it is not part of a layout shown in the layout view, this " |
| 179 | + "method will return an empty box. Otherwise, it will return a \\Box object, representing the bounding box " |
| 180 | + "of the text object, including the label's character representation.\n" |
| 181 | + "\n" |
| 182 | + "The bounding box is given as an equivalent integer-unit \\Box object, when placed in the same cell and on the same layer as the original text object." |
| 183 | + ) + |
| 184 | + gsi::method ("bbox", &gsi::TextInfo::bbox_from_text, gsi::arg ("text"), gsi::arg ("cv_index"), gsi::arg ("layer_index", -1), |
| 185 | + "@brief Obtains the bounding box for the given text object\n" |
| 186 | + "\n" |
| 187 | + "This method returns a \\Box object, representing the bounding box of the integer-unit \\Text object.\n" |
| 188 | + "The cellview index needs to be specified, while the layer index is optional. The layer index is the layer where the text object " |
| 189 | + "lives in the layout, given by the cellview index. Without a layer, the bounding box computation will not take into account potential " |
| 190 | + "additional transformations implied by transformations present the layer view specification.\n" |
| 191 | + "\n" |
| 192 | + "The bounding box is given as an equivalent integer-unit \\Box object, when placed in the same cell and on the same layer as the original text object." |
| 193 | + ) + |
| 194 | + gsi::method ("bbox", &gsi::TextInfo::bbox_from_dtext, gsi::arg ("dtext"), gsi::arg ("cv_index", -1), gsi::arg ("layer_index", -1), |
| 195 | + "@brief Obtains the bounding box for the given micrometer-unit text object\n" |
| 196 | + "\n" |
| 197 | + "This method returns a \\DBox object, representing the bounding box of the micrometer-unit \\DText object.\n" |
| 198 | + "The cellview and layer index is optional. Without a layer and cellview index, the bounding box computation will not take into account potential " |
| 199 | + "additional transformations implied by transformations present the layer view specification.\n" |
| 200 | + "\n" |
| 201 | + "The bounding box is given as an equivalent micrometer-unit \\DBox object, when placed in the same cell and on the same layer as the original text object." |
| 202 | + ), |
| 203 | + "@brief A utility class for generating text bounding boxes including the glyph polygons\n" |
| 204 | + "\n" |
| 205 | + "The geometry database regards text objects as point-like, hence the natural bounding box of a " |
| 206 | + "text object is a single point. To obtain the visual bounding box, you can use the \\TextInfo object. " |
| 207 | + "It is created from a layout view and allows computing bounding boxes from \\Text, \\DText or \\Shape objects which " |
| 208 | + "include the visual representation of the text.\n" |
| 209 | + "\n" |
| 210 | + "That bounding box is given in the equivalent space of the original text object - i.e. when it is placed into the same cell " |
| 211 | + "and on the same layer than the original text.\n" |
| 212 | + "\n" |
| 213 | + "This computation is not trivial, because there are fonts that do not scale with zoom level. Hence, the equivalent bounding " |
| 214 | + "bounding box depends on the zoom factor and other transformations that control the rendering of the text. Also, a number of " |
| 215 | + "settings from the layout view - specifically default font or default text height influence the appearance of the characters " |
| 216 | + "and need to be considered. The TextInfo object takes care of these things.\n" |
| 217 | + "\n" |
| 218 | + "It does not take care however of transformations applied inside the hierarchy. Specifically, when a text object is not " |
| 219 | + "in the current top cell, different instantiation paths may exist that render different bounding boxes. Hence there not a single " |
| 220 | + "equivalent bounding box for a text object not inside the top cell. It is recommended to first transform the texts into the top " |
| 221 | + "cell before computing the bounding boxes.\n" |
| 222 | + "\n" |
| 223 | + "Here is some sample code that places boxes over each selected text object. These boxes are identical to the selection markers " |
| 224 | + "of the texts, but this identity quickly vanishes if you zoom in or out:\n" |
| 225 | + "\n" |
| 226 | + "@code\n" |
| 227 | + "begin\n" |
| 228 | + "\n" |
| 229 | + " view = RBA::LayoutView.current\n" |
| 230 | + " # Provide undo support, so it is more convenient to try out\n" |
| 231 | + " view.transaction(\"Generate true label bounding boxes\")\n" |
| 232 | + "\n" |
| 233 | + " textinfo = RBA::TextInfo::new(view)\n" |
| 234 | + " \n" |
| 235 | + " view.each_object_selected do |sel|\n" |
| 236 | + " \n" |
| 237 | + " # Ignore selected objects which are not texts\n" |
| 238 | + " sel.shape.is_text? || next\n" |
| 239 | + " \n" |
| 240 | + " # Transform the text to top level \n" |
| 241 | + " tl_text = sel.trans * sel.shape.text\n" |
| 242 | + " \n" |
| 243 | + " # Compute the bounding box\n" |
| 244 | + " bbox = textinfo.bbox(tl_text, sel.cv_index, sel.layer)\n" |
| 245 | + " \n" |
| 246 | + " # Place boxes over the original texts\n" |
| 247 | + " # Note that 'ctx_cell' is the true origin of the selection path, hence the one that 'sel.trans' applies to\n" |
| 248 | + " view.cellview(sel.cv_index).ctx_cell.shapes(sel.layer).insert(bbox)\n" |
| 249 | + " \n" |
| 250 | + " end\n" |
| 251 | + "\n" |
| 252 | + "ensure\n" |
| 253 | + " view.commit\n" |
| 254 | + "\n" |
| 255 | + "end\n" |
| 256 | + "@/code\n" |
| 257 | + "\n" |
| 258 | + "This class has been introduced in version 0.30.5." |
| 259 | +); |
| 260 | + |
| 261 | +} |
0 commit comments