Skip to content

Commit af667ef

Browse files
Merge pull request #2187 from KLayout/feature/issue-2175
Solution for issue #2175
2 parents 295ce2a + c927ed1 commit af667ef

File tree

4 files changed

+277
-2
lines changed

4 files changed

+277
-2
lines changed
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
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+
}

src/laybasic/laybasic/layMarker.cc

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,15 @@
3636
namespace lay
3737
{
3838

39-
static db::DVector text_box_enlargement (const db::DCplxTrans &vp_trans)
39+
double marker_text_border_in_pixels ()
4040
{
4141
// 4.0 is the text box border in pixels
42-
double b = 4.0 / vp_trans.mag ();
42+
return 4.0;
43+
}
44+
45+
static db::DVector text_box_enlargement (const db::DCplxTrans &vp_trans)
46+
{
47+
double b = marker_text_border_in_pixels () / vp_trans.mag ();
4348
return db::DVector (b, b);
4449
}
4550

src/laybasic/laybasic/layMarker.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ namespace lay
4747

4848
class LayoutViewBase;
4949

50+
/**
51+
* @brief Returns the marker text border in pixels
52+
*
53+
* This is the amount by which the text bounding box (including glyphs) is enlarged
54+
* in pixels for better readability of the labels when they are selected.
55+
*/
56+
double marker_text_border_in_pixels ();
57+
5058
/**
5159
* @brief The marker base class
5260
*

src/laybasic/laybasic/laybasic.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ SOURCES += \
3333
gsiDeclLayLayoutViewBase.cc \
3434
gsiDeclLayMarker.cc \
3535
gsiDeclLayMenu.cc \
36+
gsiDeclLayTextInfo.cc \
3637
gsiDeclLayTlAdded.cc \
3738
gsiDeclLayRdbAdded.cc \
3839
gsiDeclLayConfigPage.cc \

0 commit comments

Comments
 (0)