Bug Summary

File:librecad/src/lib/engine/document/entities/rs_mtext.cpp
Warning:line 133, column 37
The left operand of '==' is a garbage value

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name rs_mtext.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -fcoverage-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -resource-dir /usr/lib/llvm-18/lib/clang/18 -D _REENTRANT -D DWGSUPPORT -D MUPARSER_STATIC -D QC_APPDIR="librecad" -D LC_VERSION=2.2.2_alpha1-607-g330d41cec -D LC_PRERELEASE=true; -D QT_NO_DEBUG -D QT_SVG_LIB -D QT_PRINTSUPPORT_LIB -D QT_WIDGETS_LIB -D QT_GUI_LIB -D QT_NETWORK_LIB -D QT_CORE_LIB -I . -I /usr/include -I ../../libraries/lciconengine -I ../../libraries/libdxfrw/src -I ../../libraries/jwwlib/src -I ../../libraries/shapelib/src -I cmd -I lib/actions -I lib/actions/visual_snap -I lib/actions/options -I lib/creation -I lib/debug -I lib/engine -I lib/engine/document -I lib/engine/document/blocks -I lib/engine/document/container -I lib/engine/document/dimstyles -I lib/engine/document/entities -I lib/engine/document/entities/support -I lib/engine/document/fonts -I lib/engine/document/io -I lib/engine/document/layers -I lib/engine/document/patterns -I lib/engine/document/selection -I lib/engine/document/textstyles -I lib/engine/document/ucs -I lib/engine/document/variables -I lib/engine/document/views -I lib/engine/clipboard -I lib/engine/overlays -I lib/engine/overlays/angles_base -I lib/engine/overlays/highlight -I lib/engine/overlays/preview -I lib/engine/overlays/references -I lib/engine/overlays/crosshair -I lib/engine/overlays/info_cursor -I lib/engine/overlays/overlay_box -I lib/engine/overlays/ucs_mark -I lib/engine/undo -I lib/engine/utils -I lib/engine/settings -I lib/fileio -I lib/filters -I lib/generators -I lib/generators/makercamsvg -I lib/generators/layers -I lib/generators/image -I lib/gui -I lib/gui/grid -I lib/gui/render -I lib/gui/render/headless -I lib/gui/render/widget -I lib/information -I lib/math -I lib/modification -I lib/selection -I lib/selection/metaentity -I lib/selection/metaentity/entities -I lib/printing -I lib/properties -I actions -I actions/dock_widgets -I actions/dock_widgets/block -I actions/dock_widgets/entity_info -I actions/dock_widgets/layer -I actions/dock_widgets/library -I actions/dock_widgets/ucs_list -I actions/drawing -I actions/drawing/draw -I actions/drawing/draw/arc -I actions/drawing/draw/circle -I actions/drawing/draw/curve -I actions/drawing/draw/spline -I actions/drawing/draw/dimensions -I actions/drawing/draw/ellipse -I actions/drawing/draw/hatch -I actions/drawing/draw/image -I actions/drawing/draw/line -I actions/drawing/draw/point -I actions/drawing/draw/rect -I actions/drawing/draw/polygon -I actions/drawing/draw/misc -I actions/drawing/draw/line/shapes -I actions/drawing/draw/line/misc -I actions/drawing/draw/line/shapes/rect -I actions/drawing/draw/line/shapes/polygon -I actions/drawing/draw/polyline -I actions/drawing/draw/text -I actions/drawing/edit -I actions/drawing/info -I actions/drawing/pick -I actions/drawing/modify -I actions/drawing/pen -I actions/drawing/rel_zero -I actions/drawing/selection -I actions/drawing/snap -I actions/drawing/zoom -I actions/file -I actions/options -I actions/print_preview -I ui -I ui/action_options -I ui/action_options/circle -I ui/action_options/curve -I ui/action_options/spline -I ui/action_options/dimensions -I ui/action_options/edit -I ui/action_options/image -I ui/action_options/info -I ui/action_options/insert -I ui/action_options/line -I ui/action_options/rect -I ui/action_options/polygon -I ui/action_options/misc -I ui/action_options/modify -I ui/action_options/ellipse -I ui/action_options/other -I ui/action_options/polyline -I ui/action_options/point -I ui/action_options/print_preview -I ui/action_options/selection -I ui/action_options/snap -I ui/action_options/text -I ui/actions -I ui/components -I ui/components/relative_position_assistant -I ui/components/comboboxes -I ui/components/containers -I ui/components/creators -I ui/components/layouts -I ui/components/pen -I ui/components/status_bar -I ui/components/toolbars -I ui/components/utils -I ui/dialogs -I ui/dialogs/actions -I ui/dialogs/actions/modify -I ui/dialogs/actions/quick_selection -I ui/dialogs/modify -I ui/dialogs/entity -I ui/dialogs/creators -I ui/dialogs/file -I ui/dialogs/file/export -I ui/dialogs/file/export/layers -I ui/dialogs/file/export/image -I ui/dialogs/file/export/makercam -I ui/dialogs/main -I ui/dialogs/settings -I ui/dialogs/settings/dimstyles -I ui/dialogs/settings/dimstyles/dimstyle_manager -I ui/dialogs/settings/dimstyles/dimstyle_manager/support -I ui/dialogs/settings/options_device -I ui/dialogs/settings/options_drawing -I ui/dialogs/settings/options_general -I ui/dialogs/settings/options_widget -I ui/dialogs/settings/shortcuts -I ui/dock_widgets -I ui/dock_widgets/block_widget -I ui/dock_widgets/command_line -I ui/dock_widgets/entity_info -I ui/dock_widgets/layer_widget -I ui/dock_widgets/layers_tree -I ui/dock_widgets/library_widget -I ui/dock_widgets/pen_palette -I ui/dock_widgets/pen_wizard -I ui/dock_widgets/property_sheet -I ui/dock_widgets/property_sheet/lib -I ui/dock_widgets/property_sheet/lib/properties -I ui/dock_widgets/property_sheet/lib/view -I ui/dock_widgets/property_sheet/lib/view/edit -I ui/dock_widgets/property_sheet/lib/widgets -I ui/dock_widgets/property_sheet/lib/widgets/sheet -I ui/dock_widgets/property_sheet/metaentity -I ui/dock_widgets/property_sheet/metaentity/entities -I ui/dock_widgets/property_sheet/metaentity/entities/document -I ui/dock_widgets/property_sheet/properties -I ui/dock_widgets/property_sheet/properties/action -I ui/dock_widgets/property_sheet/properties/bool -I ui/dock_widgets/property_sheet/properties/color -I ui/dock_widgets/property_sheet/properties/double -I ui/dock_widgets/property_sheet/properties/enum -I ui/dock_widgets/property_sheet/properties/int -I ui/dock_widgets/property_sheet/properties/layer -I ui/dock_widgets/property_sheet/properties/linetype -I ui/dock_widgets/property_sheet/properties/linewidth -I ui/dock_widgets/property_sheet/properties/rect -I ui/dock_widgets/property_sheet/properties/rsvector -I ui/dock_widgets/property_sheet/properties/string -I ui/dock_widgets/views_list -I ui/dock_widgets/ucs_list -I ui/dock_widgets/workspaces -I ui/dock_widgets/cad -I ui/main -I ui/main/init -I ui/main/persistence -I ui/main/release_check -I ui/main/support -I ui/main/fontviewer -I ui/main/workspaces -I ui/view -I main -I main/console_dxf2pdf -I test -I plugins -I ../res -I ../res/arrows -I ../res/controls -I ../res/dxf -I ../res/gdt -I ../res/icons -I ../res/images -I ../../../Qt/6.9.0/gcc_64/include -I ../../../Qt/6.9.0/gcc_64/include/QtSvg -I ../../../Qt/6.9.0/gcc_64/include/QtPrintSupport -I ../../../Qt/6.9.0/gcc_64/include/QtWidgets -I ../../../Qt/6.9.0/gcc_64/include/QtGui -I ../../../Qt/6.9.0/gcc_64/include/QtNetwork -I ../../../Qt/6.9.0/gcc_64/include/QtCore -I ../../generated/librecad/moc -I ../../generated/librecad/ui -I ../../../Qt/6.9.0/gcc_64/mkspecs/linux-g++ -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/x86_64-linux-gnu/c++/14 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/backward -internal-isystem /usr/lib/llvm-18/lib/clang/18/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -std=gnu++1z -fdeprecated-macro -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/runner/work/LibreCAD/LibreCAD/out/2026-08-04-154929-5069-1 -x c++ lib/engine/document/entities/rs_mtext.cpp
1/****************************************************************************
2**
3** This file is part of the LibreCAD project, a 2D CAD program
4**
5** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl)
6** Copyright (C) 2001-2003 RibbonSoft. All rights reserved.
7**
8**
9** This file may be distributed and/or modified under the terms of the
10** GNU General Public License version 2 as published by the Free Software
11** Foundation and appearing in the file gpl-2.0.txt included in the
12** packaging of this file.
13**
14** This program is distributed in the hope that it will be useful,
15** but WITHOUT ANY WARRANTY; without even the implied warranty of
16** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17** GNU General Public License for more details.
18**
19** You should have received a copy of the GNU General Public License
20** along with this program; if not, write to the Free Software
21** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22**
23** This copyright notice MUST APPEAR in all copies of the script!
24**
25**********************************************************************/
26
27#include "rs_mtext.h"
28
29#include <iostream>
30
31#include "rs_debug.h"
32#include "rs_font.h"
33#include "rs_fontlist.h"
34#include "rs_insert.h"
35#include "rs_line.h"
36#include "rs_math.h"
37#include "rs_painter.h"
38#include "rs_pen.h"
39
40// Implementation of the Unicode Bidirectional Algorithm (UAX #9), simplified
41// for paragraphs without explicit embedding/override controls or isolates —
42// which covers all DXF/DWG MText input. Per-character bidi class comes from
43// QChar::direction() (Qt's Unicode tables), so we still rely on Qt's data
44// even though we don't go through QTextLayout (which depends on the font
45// database and would silently degrade for scripts that lack a fallback font
46// on the host system).
47std::vector<int>
48RS_MText::computeBidiVisualOrder(const QString &text,
49 Qt::LayoutDirection baseDirection) {
50 std::vector<int> result;
51 const int n = text.size();
52 if (n == 0) {
1
Assuming 'n' is not equal to 0
53 return result;
54 }
55
56 const int baseLevel = (baseDirection == Qt::RightToLeft) ? 1 : 0;
2
Assuming 'baseDirection' is not equal to RightToLeft
3
'?' condition is false
57
58 // ---------- Step 1: assign embedding levels per character ----------
59 // Levels: 0 = LTR, 1 = RTL, 2 = numeric (LTR within RTL context).
60 // Strong directionality directly determines levels for L / R / AL.
61 // Numeric types (EN, AN) get the +2 bump (LTR digits inside RTL get
62 // visually rendered LTR but "after" the surrounding RTL context).
63 // Neutrals/weaks are filled in below from surrounding strong context.
64 constexpr int LVL_UNKNOWN = -1;
65 std::vector<int> level(n, LVL_UNKNOWN);
66 for (int i = 0; i < n; ++i) {
4
Assuming 'i' is < 'n'
5
Loop condition is true. Entering loop body
8
Assuming 'i' is >= 'n'
9
Loop condition is false. Execution continues on line 107
67 switch (text.at(i).direction()) {
6
Control jumps to 'case DirL:' at line 68
68 case QChar::DirL:
69 level[i] = 0;
70 break;
7
Execution continues on line 66
71 case QChar::DirR:
72 case QChar::DirAL:
73 level[i] = 1;
74 break;
75 case QChar::DirEN:
76 case QChar::DirAN:
77 // Digits are LTR by themselves but get bumped one level above
78 // base if base is LTR (level 2), or above the next-level RTL
79 // context (also level 2). Per UAX#9, EN/AN consistently get
80 // even levels >= base+2 in mixed paragraphs.
81 level[i] = (baseLevel == 1) ? 2 : 2;
82 break;
83 default:
84 // Neutrals (whitespace, punctuation, paragraph/segment separators,
85 // boundary-neutrals, NSM, BN, etc.) — resolved in pass 2.
86 level[i] = LVL_UNKNOWN;
87 break;
88 }
89 }
90
91 // ---------- Step 1.5: UAX#9 N0 — bracket-pair handling ----------------
92 // Identify Unicode bidi paired brackets (BD16). When both brackets in a
93 // pair end up in the same direction context, N0 keeps them as a unit
94 // so they don't drift apart under N1/N2 fallback. Pair table is the
95 // Unicode 15.x BidiBrackets.txt set (~65 pairs); canonical equivalents
96 // (e.g. U+2329 ↔ U+27E8) are handled by direct enumeration rather than
97 // a runtime canonical-decomposition pass.
98 {
99 // Sorted by `open`. Binary search keeps the per-character lookup
100 // O(log n); n is small enough that linear would also be fine but
101 // the tabular form documents the data clearly.
102 struct BracketPairCP {
103 ushort open;
104 ushort close;
105 };
106 constexpr BracketPairCP kBracketPairs[] = {
107 {0x0028, 0x0029}, {0x005B, 0x005D}, {0x007B, 0x007D}, {0x0F3A, 0x0F3B},
108 {0x0F3C, 0x0F3D}, {0x169B, 0x169C}, {0x2045, 0x2046}, {0x207D, 0x207E},
109 {0x208D, 0x208E}, {0x2308, 0x2309}, {0x230A, 0x230B}, {0x2329, 0x232A},
110 {0x2768, 0x2769}, {0x276A, 0x276B}, {0x276C, 0x276D}, {0x276E, 0x276F},
111 {0x2770, 0x2771}, {0x2772, 0x2773}, {0x2774, 0x2775}, {0x27C5, 0x27C6},
112 {0x27E6, 0x27E7}, {0x27E8, 0x27E9}, {0x27EA, 0x27EB}, {0x27EC, 0x27ED},
113 {0x27EE, 0x27EF}, {0x2983, 0x2984}, {0x2985, 0x2986}, {0x2987, 0x2988},
114 {0x2989, 0x298A}, {0x298B, 0x298C}, {0x298D, 0x298E}, {0x298F, 0x2990},
115 {0x2991, 0x2992}, {0x2993, 0x2994}, {0x2995, 0x2996}, {0x2997, 0x2998},
116 {0x29D8, 0x29D9}, {0x29DA, 0x29DB}, {0x29FC, 0x29FD}, {0x2E22, 0x2E23},
117 {0x2E24, 0x2E25}, {0x2E26, 0x2E27}, {0x2E28, 0x2E29}, {0x2E55, 0x2E56},
118 {0x2E57, 0x2E58}, {0x2E59, 0x2E5A}, {0x2E5B, 0x2E5C}, {0x3008, 0x3009},
119 {0x300A, 0x300B}, {0x300C, 0x300D}, {0x300E, 0x300F}, {0x3010, 0x3011},
120 {0x3014, 0x3015}, {0x3016, 0x3017}, {0x3018, 0x3019}, {0x301A, 0x301B},
121 {0xFE59, 0xFE5A}, {0xFE5B, 0xFE5C}, {0xFE5D, 0xFE5E}, {0xFF08, 0xFF09},
122 {0xFF3B, 0xFF3D}, {0xFF5B, 0xFF5D}, {0xFF5F, 0xFF60}, {0xFF62, 0xFF63},
123 };
124 constexpr int kBracketPairsCount =
10
Uninitialized value stored to field 'open'
125 sizeof(kBracketPairs) / sizeof(kBracketPairs[0]);
126
127 auto matchingClose = [&](QChar c) -> ushort {
128 const ushort u = c.unicode();
129 // Binary search by open codepoint.
130 int lo = 0, hi = kBracketPairsCount;
131 while (lo < hi) {
13
Loop condition is true. Entering loop body
132 const int mid = (lo + hi) / 2;
133 if (kBracketPairs[mid].open == u)
14
The left operand of '==' is a garbage value
134 return kBracketPairs[mid].close;
135 if (kBracketPairs[mid].open < u)
136 lo = mid + 1;
137 else
138 hi = mid;
139 }
140 return 0;
141 };
142 auto isClose = [&](QChar c) -> bool {
143 const ushort u = c.unicode();
144 for (int k = 0; k < kBracketPairsCount; ++k) {
145 if (kBracketPairs[k].close == u)
146 return true;
147 }
148 return false;
149 };
150 // BD16 stack: (matching-close codepoint, opening position).
151 std::vector<std::pair<ushort, int>> stack;
152 struct BracketPair {
153 int open;
154 int close;
155 };
156 std::vector<BracketPair> pairs;
157 for (int k = 0; k < n; ++k) {
11
Loop condition is true. Entering loop body
158 const QChar c = text.at(k);
159 const ushort u = c.unicode();
160 const ushort closer = matchingClose(c);
12
Calling 'operator()'
161 if (closer != 0) {
162 if (stack.size() < 63) { // UAX#9 BD16 limit
163 stack.emplace_back(closer, k);
164 }
165 } else if (isClose(c)) {
166 for (int s = static_cast<int>(stack.size()) - 1; s >= 0; --s) {
167 if (stack[s].first == u) {
168 pairs.push_back({stack[s].second, k});
169 stack.resize(s); // pop everything above too
170 break;
171 }
172 }
173 }
174 }
175 const int embeddingLevel = baseLevel;
176 for (const auto &p : pairs) {
177 // Find first strong type (L vs R/AL) inside the pair.
178 int strongInside = LVL_UNKNOWN; // 0 (L) or 1 (R)
179 bool matchedEmbedding = false;
180 for (int k = p.open + 1; k < p.close && !matchedEmbedding; ++k) {
181 const QChar::Direction d = text.at(k).direction();
182 int s = 0;
183 if (d == QChar::DirL)
184 s = 0;
185 else if (d == QChar::DirR || d == QChar::DirAL)
186 s = 1;
187 else
188 continue;
189 if (strongInside == LVL_UNKNOWN)
190 strongInside = s;
191 if (s == embeddingLevel)
192 matchedEmbedding = true;
193 }
194 int bracketLevel = 0;
195 if (matchedEmbedding) {
196 bracketLevel = embeddingLevel;
197 } else if (strongInside != LVL_UNKNOWN) {
198 // Strong-but-opposite inside; check preceding strong context.
199 int context = embeddingLevel;
200 for (int k = p.open - 1; k >= 0; --k) {
201 const QChar::Direction d = text.at(k).direction();
202 if (d == QChar::DirL) {
203 context = 0;
204 break;
205 }
206 if (d == QChar::DirR || d == QChar::DirAL) {
207 context = 1;
208 break;
209 }
210 }
211 bracketLevel =
212 (context == strongInside) ? strongInside : embeddingLevel;
213 } else {
214 continue; // no strong inside — leave as ON for N1/N2.
215 }
216 level[p.open] = bracketLevel;
217 level[p.close] = bracketLevel;
218 }
219 }
220
221 // ---------- Step 2: resolve neutrals via surrounding strong context ----
222 // A run of unresolved characters takes the level of the strong character
223 // before it if that matches the strong character after it; otherwise it
224 // takes the paragraph base level. This collapses UAX#9 rules N1/N2
225 // into one pass, sufficient for our MText inputs.
226 int i = 0;
227 while (i < n) {
228 if (level[i] != LVL_UNKNOWN) {
229 ++i;
230 continue;
231 }
232 int runStart = i;
233 while (i < n && level[i] == LVL_UNKNOWN)
234 ++i;
235 const int runEnd = i; // exclusive
236
237 int prevLevel = baseLevel;
238 for (int k = runStart - 1; k >= 0; --k) {
239 if (level[k] != LVL_UNKNOWN && level[k] < 2) {
240 prevLevel = level[k];
241 break;
242 }
243 }
244 int nextLevel = baseLevel;
245 for (int k = runEnd; k < n; ++k) {
246 if (level[k] != LVL_UNKNOWN && level[k] < 2) {
247 nextLevel = level[k];
248 break;
249 }
250 }
251 const int neutralLevel = (prevLevel == nextLevel) ? prevLevel : baseLevel;
252 for (int k = runStart; k < runEnd; ++k) {
253 level[k] = neutralLevel;
254 }
255 }
256
257 // ---------- Step 3: apply L2 — reverse maximal runs of equal level ----
258 // Build the visual order by iterating from the highest level down.
259 result.reserve(n);
260 for (int k = 0; k < n; ++k)
261 result.push_back(k);
262
263 // Find max level present.
264 int maxLevel = 0;
265 for (int k = 0; k < n; ++k) {
266 if (level[k] > maxLevel)
267 maxLevel = level[k];
268 }
269 // Per UAX#9 L2: reverse runs of level >= L for L from maxLevel down to
270 // 1 (the lowest odd level) inclusive — regardless of base direction.
271 // For LTR base this leaves L runs untouched and flips RTL/numeric runs;
272 // for RTL base it additionally reverses the entire paragraph at L=1, so
273 // strong-LTR runs end up in the right place within an RTL frame.
274 for (int L = maxLevel; L >= 1; --L) {
275 int j = 0;
276 while (j < n) {
277 if (level[result[j]] >= L) {
278 int runStart = j;
279 while (j < n && level[result[j]] >= L)
280 ++j;
281 std::reverse(result.begin() + runStart, result.begin() + j);
282 } else {
283 ++j;
284 }
285 }
286 }
287 return result;
288}
289
290RS_MText::LC_TextLine* RS_MText::LC_TextLine::clone() const {
291 auto* ec = new LC_TextLine(getParent(), isOwner());
292 if (isOwner()) {
293 for (const RS_Entity* entity : *this) {
294 if (entity != nullptr) {
295 ec->push_back(entity->clone());
296 }
297 }
298 }
299 else {
300 ec->clear();
301 std::copy(cbegin(), cend(), std::back_inserter(*ec));
302 }
303 ec->detach();
304 ec->setTextSize(m_textSize);
305 ec->setLeftBottomCorner(m_leftBottomCorner);
306 ec->setBaselineStart(m_baselineStart);
307 ec->setBaselineEnd(m_baselineEnd);
308 return ec;
309}
310
311const RS_Vector& RS_MText::LC_TextLine::getTextSize() const {
312 return m_textSize;
313}
314
315void RS_MText::LC_TextLine::setTextSize(const RS_Vector& v) {
316 m_textSize = v;
317}
318
319const RS_Vector& RS_MText::LC_TextLine::getLeftBottomCorner() const {
320 return m_leftBottomCorner;
321}
322
323void RS_MText::LC_TextLine::setLeftBottomCorner(const RS_Vector& v) {
324 m_leftBottomCorner = v;
325}
326
327const RS_Vector& RS_MText::LC_TextLine::getBaselineStart() const {
328 return m_baselineStart;
329}
330
331void RS_MText::LC_TextLine::setBaselineStart(const RS_Vector& v) {
332 m_baselineStart = v;
333}
334
335const RS_Vector& RS_MText::LC_TextLine::getBaselineEnd() const {
336 return m_baselineEnd;
337}
338
339void RS_MText::LC_TextLine::setBaselineEnd(const RS_Vector& v) {
340 m_baselineEnd = v;
341}
342
343void RS_MText::LC_TextLine::moveBaseline(const RS_Vector& offset) {
344 m_baselineStart.move(offset);
345 m_baselineEnd.move(offset);
346 m_leftBottomCorner.move(offset);
347}
348
349RS_MTextData::RS_MTextData(const RS_Vector& insertionPoint, const double height, const double width, const VAlign valign,
350 const HAlign halign, const MTextDrawingDirection drawingDirection,
351 const MTextLineSpacingStyle lineSpacingStyle, const double lineSpacingFactor, const QString& text,
352 const QString& style, const double angle, const RS2::UpdateMode updateMode)
353 : insertionPoint(insertionPoint), height(height), width(width), valign(valign), halign(halign),
354 drawingDirection(drawingDirection), lineSpacingStyle(lineSpacingStyle), lineSpacingFactor(lineSpacingFactor), text(text),
355 style(style), angle(angle), updateMode(updateMode) {
356}
357
358std::ostream& operator<<(std::ostream& os, const RS_MTextData& td) {
359 os << "(" << td.insertionPoint << ',' << td.height << ',' << td.width << ',' << td.valign << ',' << td.halign << ',' << td.
360 drawingDirection << ',' << td.lineSpacingStyle << ',' << td.lineSpacingFactor << ',' << td.text.toLatin1().data() << ',' << td.style
361 .toLatin1().data() << ',' << td.angle << ',' << td.updateMode << ',' << ")";
362 return os;
363}
364
365/**
366 * Constructor.
367 */
368RS_MText::RS_MText(RS_EntityContainer* parent, const RS_MTextData& d)
369 : RS_EntityContainer(parent), m_data(d) {
370 RS_MText::setText(m_data.text);
371}
372
373RS_Entity* RS_MText::clone() const {
374 auto* t = new RS_MText(*this);
375 t->setOwner(isOwner());
376 t->detach();
377 return t;
378}
379
380// fixme - test concept for using UI proxies for heavy entities on modification operation (rotate, scale etc).
381// potentially, it might be either expanded further or removed.
382class RS_MTextProxy : public RS_EntityContainer {
383public:
384 explicit RS_MTextProxy(const RS_MText& parent) : RS_EntityContainer(parent) {
385 }
386};
387
388RS_Entity* RS_MText::cloneProxy() const {
389 auto* proxy = new RS_EntityContainer();
390 proxy->setOwner(true);
391 for (RS_Entity* entity : std::as_const(*this)) {
392 const auto line = dynamic_cast<LC_TextLine*>(entity);
393 if (line != nullptr && line->count() > 0) {
394 const RS_Vector& start = line->getBaselineStart();
395 const RS_Vector& end = line->getBaselineEnd();
396 const auto l = new RS_Line(proxy, start, end);
397 proxy->addEntity(l);
398 }
399 }
400 return proxy;
401}
402
403/**
404 * Sets a new text. The entities representing the
405 * text are updated.
406 */
407void RS_MText::setText(QString t) {
408 m_data.text = std::move(t);
409
410 // handle some special flags embedded in the text:
411 if (m_data.text.left(4) == R"(\A0;)") {
412 m_data.text = m_data.text.mid(4);
413 m_data.valign = RS_MTextData::VABottom;
414 }
415 else if (m_data.text.left(4) == R"(\A1;)") {
416 m_data.text = m_data.text.mid(4);
417 m_data.valign = RS_MTextData::VAMiddle;
418 }
419 else if (m_data.text.left(4) == R"(\A2;)") {
420 m_data.text = m_data.text.mid(4);
421 m_data.valign = RS_MTextData::VATop;
422 }
423
424 if (m_data.updateMode == RS2::Update) {
425 update();
426 }
427}
428
429void RS_MText::setDrawingDirection(RS_MTextData::MTextDrawingDirection direction) {
430 if (m_data.drawingDirection == direction) {
431 return;
432 }
433 m_data.drawingDirection = direction;
434 if (m_data.updateMode == RS2::Update) { // fixme - why update is only on some setters?
435 update();
436 }
437}
438
439/**
440 * Gets the alignment as an int.
441 *
442 * @return 1: top left ... 9: bottom right
443 */
444int RS_MText::getAlignment() const {
445 if (m_data.valign == RS_MTextData::VATop) {
446 if (m_data.halign == RS_MTextData::HALeft) {
447 return 1;
448 }
449 if (m_data.halign == RS_MTextData::HACenter) {
450 return 2;
451 }
452 if (m_data.halign == RS_MTextData::HARight) {
453 return 3;
454 }
455 }
456 else if (m_data.valign == RS_MTextData::VAMiddle) {
457 if (m_data.halign == RS_MTextData::HALeft) {
458 return 4;
459 }
460 if (m_data.halign == RS_MTextData::HACenter) {
461 return 5;
462 }
463 if (m_data.halign == RS_MTextData::HARight) {
464 return 6;
465 }
466 }
467 else if (m_data.valign == RS_MTextData::VABottom) {
468 if (m_data.halign == RS_MTextData::HALeft) {
469 return 7;
470 }
471 if (m_data.halign == RS_MTextData::HACenter) {
472 return 8;
473 }
474 if (m_data.halign == RS_MTextData::HARight) {
475 return 9;
476 }
477 }
478
479 return 1;
480}
481
482/**
483 * Sets the alignment from an int.
484 *
485 * @param a 1: top left ... 9: bottom right
486 */
487void RS_MText::setAlignment(const int a) {
488 switch (a % 3) {
489 case 1:
490 m_data.halign = RS_MTextData::HALeft;
491 break;
492 case 2:
493 m_data.halign = RS_MTextData::HACenter;
494 break;
495 case 0:
496 m_data.halign = RS_MTextData::HARight;
497 break;
498 default:
499 m_data.halign = RS_MTextData::HALeft;
500 break;
501 }
502
503 const int valign = static_cast<int>(ceil(a / 3.0));
504 switch (valign) {
505 case 1:
506 m_data.valign = RS_MTextData::VATop;
507 break;
508 case 2:
509 m_data.valign = RS_MTextData::VAMiddle;
510 break;
511 case 3:
512 m_data.valign = RS_MTextData::VABottom;
513 break;
514 default:
515 m_data.valign = RS_MTextData::VATop;
516 break;
517 }
518}
519
520/**
521 * @return Number of lines in this text entity.
522 */
523int RS_MText::getNumberOfLines() const {
524 return 1 + std::count_if(m_data.text.cbegin(), m_data.text.cend(), [](QChar c) {
525 return c.unicode() == 0xA;
526 });
527}
528
529/**
530 * Updates the Inserts (letters) of this text. Called when the
531 * text or it's m_data, position, alignment, .. changes.
532 * This method also updates the usedTextWidth / usedTextHeight property.
533 */
534void RS_MText::update() {
535 RS_DEBUGRS_Debug::instance()->print("RS_MText::update");
536
537 clear();
538 if (isDeleted()) {
539 return;
540 }
541
542 m_usedTextWidth = 0.0;
543 m_usedTextHeight = 0.0;
544
545 RS_Font* font{RS_FONTLISTRS_FontList::instance()->requestFont(m_data.style)};
546 if (nullptr == font) {
547 return;
548 }
549
550 // letterSpace and space are always treated as positive advances here; the
551 // bidi pass already produced visual L→R order, so the cursor unconditionally
552 // moves rightward during emission.RS_Vector letterPos{0.0, -9.0};
553 RS_Vector letterPos{0.0, -9.0};
554 const RS_Vector letterSpace{font->getLetterSpacing(), 0.0};
555 const double spaceWidth = font->getWordSpacing();
556
557 int lineCounter{0};
558
559 // Every single text line gets stored in this entity container
560 // so we can move the whole line around easily:
561 auto oneLine{new LC_TextLine(this)};
562
563 // First every text line is created with
564 // alignment: top left
565 // angle: 0
566 // height: 9.0
567 // Rotation, scaling and centering is done later
568
569 // Logical-order pass: process MText format codes and accumulate per-line
570 // segments. Each line's segments are then bidi-reordered and emitted via
571 // flushBidiLine(); see the comment on LC_BidiSegment in the header.
572 std::vector<LC_BidiSegment> segments;
573
574 auto closeLine = [&]() {
575 flushBidiLine(*oneLine, segments, letterSpace, letterPos);
576 updateAddLine(oneLine, lineCounter++);
577 oneLine = new LC_TextLine(this);
578 letterPos = RS_Vector(0.0, -9.0);
579 };
580 for (decltype(m_data.text.length()) i = 0; i < m_data.text.length(); ++i) {
581 auto remaining = m_data.text.mid(i);
582 // Handle \F not followed by {<codePage>}
583 if (remaining.startsWith(R"(\F)") && remaining.indexOf(R"(^\\[Ff]\{[\d\w]*\})") != 0) {
584 segments.push_back(
585 {LC_BidiSegment::Char, m_data.text.at(i), font, 0.0, {}, {}});
586 continue;
587 }
588 if (remaining.startsWith(R"(\\)")) {
589 // Allow escape '\', needed to support "\S" and "\P" in string
590 // "\S" is used for super/subscripts
591 // "\P" is used to start a new line
592 // "\\S" and "\\P" to get literal strings "\S" and "\P"
593 segments.push_back(
594 {LC_BidiSegment::Char, m_data.text.at(i++), font, 0.0, {}, {}});
595 continue;
596 }
597
598 switch (m_data.text.at(i).unicode()) {
599 case 0x0A:
600 // line feed:
601 closeLine();
602 break;
603
604 case 0x20:
605 // Space:
606 segments.push_back(
607 {LC_BidiSegment::Space, QChar(' '), nullptr, spaceWidth, {}, {}});
608 break;
609
610 case 0x5C: {
611 bool handled{false};
612 // code (e.g. \S, \P, ..)
613 ++i;
614 if (static_cast<int>(m_data.text.length()) <= i) {
615 continue;
616 }
617 // `i` is a UTF-16 code-unit index into data.text; index the QString
618 // directly. The previous `data.text.toUcs4().at(i)` indexed a UCS-4
619 // array with a UTF-16 offset — out of bounds / wrong codepoint once any
620 // non-BMP (surrogate-pair) char precedes `i`. All inner cases below are
621 // ASCII ('P','f','F','S'), so a BMP code unit is sufficient and a
622 // non-BMP unit correctly falls to default (--i).
623 char16_t ch{m_data.text.at(i).unicode()};
624 switch (ch) {
625 case 'P':
626 closeLine();
627 handled = true;
628 break;
629
630 case 'f':
631 case 'F': {
632 // font change
633 // \f{symbol} changes font to symbol
634 // \f{} sets font to standard
635 ++i;
636 if ('{' != m_data.text.at(i).unicode()) {
637 --i;
638 continue;
639 }
640
641 const qsizetype j{m_data.text.indexOf('}', i)};
642 if (j > i) {
643 QString fontName = m_data.text.mid(i + 1, j - i - 1);
644
645 const auto requestedFont = RS_FONTLISTRS_FontList::instance()->requestFont(fontName);
646 if (requestedFont != nullptr) {
647 font = requestedFont;
648 }
649 else {
650 font = RS_FONTLISTRS_FontList::instance()->requestFont("standard");
651 }
652 i = j;
653 }
654 continue;
655 } // inner case 'f','F'
656
657 case 'S': {
658 QString upperText;
659 QString lowerText;
660
661 // get upper string:
662 ++i; // fixme - sand - rework cycles
663 while (static_cast<int>(m_data.text.length()) > i && m_data.text.at(i).unicode() != '^' && m_data.text.at(i).unicode() !=
664 '\\') {
665 upperText += m_data.text.at(i);
666 ++i;
667 }
668
669 ++i;
670 if (static_cast<int>(m_data.text.length()) > i && '^' == m_data.text.at(i - 1).unicode() && ' ' == m_data.text.at(i).
671 unicode()) {
672 ++i;
673 }
674
675 // get lower string:
676 while (static_cast<int>(m_data.text.length()) > i && ';' != m_data.text.at(i).unicode()) {
677 lowerText += m_data.text.at(i);
678 ++i;
679 }
680
681 LC_BidiSegment stack;
682 stack.kind = LC_BidiSegment::Stack;
683 stack.upperText = std::move(upperText);
684 stack.lowerText = std::move(lowerText);
685 segments.push_back(std::move(stack));
686 handled = true;
687
688 break;
689 } // inner case 'S'
690
691 default:
692 --i;
693 break;
694 } // inner switch (ch)
695
696 if (handled) {
697 break;
698 }
699 } // outer case 0x5C
700
701 // if char is not handled
702 // fall-through
703 default: {
704 // One Letter:
705 segments.push_back(
706 {LC_BidiSegment::Char, m_data.text.at(i), font, 0.0, {}, {}});
707 break;
708 } // outer default
709 } // outer switch (m_data.text.at(i).unicode())
710 } // for (i) loop
711
712 // Flush any trailing segments belonging to the last line.
713 flushBidiLine(*oneLine, segments, letterSpace, letterPos);
714
715 m_usedTextHeight -=
716 m_data.height * m_data.lineSpacingFactor * 5.0 / 3.0 - m_data.height;
717
718 updateAddLine(oneLine, lineCounter);
719
720 alignVertically();
721 RS_DEBUGRS_Debug::instance()->print("RS_MText::update: OK");
722}
723
724/**
725 * Reorder @p segments via the Unicode Bidirectional Algorithm and emit each
726 * one (letter / space / super-sub stack) into @p oneLine in visual L→R order.
727 * Cursor advances are computed from @c .lff metrics, not from QTextLayout's
728 * Qt-font metrics — QTextLayout is used only for the logical→visual
729 * permutation. Clears @p segments on return.
730 */
731void RS_MText::flushBidiLine(LC_TextLine &oneLine,
732 std::vector<LC_BidiSegment> &segments,
733 const RS_Vector &letterSpace,
734 RS_Vector &letterPosition) {
735 if (segments.empty()) {
736 return;
737 }
738
739 // Build a plain-text string for the bidi pass. Stacks become U+FFFC (Object
740 // Replacement Character), which has bidi class ON (Other Neutral) so it
741 // gets resolved to the surrounding context like punctuation.
742 QString plainText;
743 plainText.reserve(static_cast<int>(segments.size()));
744 for (const auto &seg : segments) {
745 switch (seg.kind) {
746 case LC_BidiSegment::Char:
747 plainText.append(seg.codepoint);
748 break;
749 case LC_BidiSegment::Space:
750 plainText.append(QChar(' '));
751 break;
752 case LC_BidiSegment::Stack:
753 plainText.append(QChar(0xFFFC));
754 break;
755 }
756 }
757
758 // For an explicit RightToLeft setting, lay out characters in pure
759 // positional reverse — matching AutoCAD's `drawingDirection` semantics and
760 // the property-panel/dialog widget mirrors. UAX#9 leaves EN digits
761 // direction-immune, so a UAX#9 pass would render "1234" identically in LTR
762 // and RTL on the canvas while the widget shows "4321" — diverging from the
763 // user's edited buffer. The other settings (LeftToRight, TopToBottom,
764 // ByStyle) keep going through UAX#9 with an LTR base so embedded
765 // strong-RTL runs (Hebrew/Arabic) still display correctly.
766 //
767 // Surrogate-pair caveat: segments are built one QChar each, so a non-BMP
768 // codepoint already arrives as two adjacent segments. Reversal swaps the
769 // halves; rendering then fails the font lookup on each half and falls
770 // back to U+FFFD anyway (addLetter is QChar-scoped). Fixing this needs
771 // grapheme-aware segment building plus codepoint-based font lookup —
772 // broader rendering work, not bidi.
773 std::vector<int> visualOrder;
774 if (m_data.drawingDirection == RS_MTextData::RightToLeft) {
775 visualOrder.resize(segments.size());
776 for (size_t i = 0; i < segments.size(); ++i) {
777 visualOrder[i] = static_cast<int>(segments.size() - 1 - i);
778 }
779 } else {
780 visualOrder = computeBidiVisualOrder(plainText, Qt::LeftToRight);
781 }
782
783 for (int logIdx : visualOrder) {
784 const auto &seg = segments[logIdx];
785 switch (seg.kind) {
786 case LC_BidiSegment::Char: {
787 RS_Font *segFont = seg.font;
788 if (segFont == nullptr) {
789 // Fallback: should not normally happen, but stay defensive.
790 segFont = RS_FONTLISTRS_FontList::instance()->requestFont(m_data.style);
791 }
792 if (segFont != nullptr) {
793 addLetter(oneLine, seg.codepoint, *segFont, letterSpace,
794 letterPosition);
795 }
796 break;
797 }
798 case LC_BidiSegment::Space:
799 letterPosition.x += seg.width;
800 break;
801 case LC_BidiSegment::Stack: {
802 double upperWidth = 0.0;
803 if (!seg.upperText.isEmpty()) {
804 RS_MText *upper = createUpperLower(seg.upperText, m_data,
805 letterPosition + RS_Vector{0., 9.});
806 oneLine.addEntity(upper);
807 upper->reparent(&oneLine);
808 upperWidth = upper->getSize().x;
809 }
810 double lowerWidth = 0.0;
811 if (!seg.lowerText.isEmpty()) {
812 RS_MText *lower = createUpperLower(seg.lowerText, m_data,
813 letterPosition + RS_Vector{0., 4.});
814 oneLine.addEntity(lower);
815 lower->reparent(&oneLine);
816 lowerWidth = lower->getSize().x;
817 }
818 letterPosition.x += std::max(upperWidth, lowerWidth);
819 letterPosition += letterSpace;
820 break;
821 }
822 }
823 }
824
825 segments.clear();
826}
827
828void RS_MText::alignVertically() {
829 // Vertical Align:
830 switch (m_data.valign) {
831 case RS_MTextData::VATop: {
832 // no change
833 break;
834 }
835 case RS_MTextData::VAMiddle: {
836 RS_EntityContainer::move({0., 0.5 * m_usedTextHeight});
837 break;
838 }
839 case RS_MTextData::VABottom: {
840 // adjust corners for text lines
841 RS_EntityContainer::move({0., m_usedTextHeight});
842 break;
843 }
844 default: LC_ERRRS_Debug::Log(RS_Debug::D_ERROR) << __func__ << "(): line " << __LINE__844 << ": invalid Invalid RS_MText::VAlign=" << m_data.valign;
845 break;
846 }
847 for (RS_Entity* e : std::as_const(*this)) {
848 const auto line = dynamic_cast<LC_TextLine*>(e);
849 if (line != nullptr) {
850 auto corner = RS_Vector(line->getMin().rotate(m_data.insertionPoint, m_data.angle));
851 RS_Vector size = line->getSize();
852 line->setLeftBottomCorner(corner);
853 line->setTextSize(size);
854 }
855 }
856 rotateLinesRefs();
857
858 RS_EntityContainer::rotate(m_data.insertionPoint, m_data.angle);
859
860 forcedCalculateBorders();
861}
862
863void RS_MText::rotateLinesRefs() const {
864 for (RS_Entity* e : std::as_const(*this)) {
865 const auto line = dynamic_cast<LC_TextLine*>(e);
866 if (line != nullptr) {
867 RS_Vector corner = line->getLeftBottomCorner();
868 const RS_Vector size = line->getTextSize();
869 RS_Vector shiftVector(size.y / 2, 0);
870 shiftVector.rotate(m_data.angle + M_PI_21.57079632679489661923);
871
872 RS_Vector baselineStart = corner + shiftVector;
873 RS_Vector baselineEnd = corner + shiftVector + RS_Vector::polar(size.x, m_data.angle);
874
875 // baselineStart.rotate(m_data.insertionPoint, m_data.angle);
876 line->setBaselineStart(baselineStart);
877
878 // baselineEnd.rotate(m_data.insertionPoint, m_data.angle);
879 line->setBaselineEnd(baselineEnd);
880 }
881 }
882}
883
884/**
885 * Used internally by update() to add a letter to one line
886 *
887 * @param oneLine
888 * @param letter
889 * @param font
890 * @param letterSpace
891 * @param letterPosition
892 * @param oneLine the current entity container
893 * @param letter the letter to add
894 * @param font the font to use
895 * @param letterSpace the letter width to use
896 * @param letterPosition the current letter position; will be updated
897 * after addition
898 *
899 */
900void RS_MText::addLetter(LC_TextLine& oneLine, const QChar letter, RS_Font& font, const RS_Vector& letterSpace, RS_Vector& letterPosition) {
901 auto letterText{QString(letter)};
902 if (nullptr == font.findLetter(letterText)) {
903 RS_DEBUGRS_Debug::instance()->print("RS_MText::update: missing font for letter( %s ), replaced " "it with QChar(0xfffd)", qPrintable(letterText)QtPrivate::asString(letterText).toLocal8Bit().constData());
904 letterText = QChar(0xfffd);
905 }
906
907 LC_LOGRS_Debug::Log() << "RS_MText::update: insert a letter at pos:(" << letterPosition.x << ", " << letterPosition.y << ")";
908
909 const RS_InsertData d(letterText, letterPosition, RS_Vector(1.0, 1.0), 0.0, 1, 1,
910 RS_Vector(0.0, 0.0), font.getLetterList(), RS2::NoUpdate);
911
912 const auto letterEntity{new RS_Insert(this, d)};
913 letterEntity->setPen(RS_Pen(RS2::FlagInvalid));
914 letterEntity->setLayer(nullptr);
915 letterEntity->update();
916 letterEntity->forcedCalculateBorders();
917
918 // Add spacing, if the font is actually wider than word spacing
919 double actualWidth = letterEntity->getMax().x - letterEntity->getMin().x;
920 if (actualWidth >= font.getWordSpacing() + RS_TOLERANCE1.0e-10) {
921 const double letterSpacing = std::max(1., letterSpace.x);
922 const double wordSpacing = font.getWordSpacing();
923 actualWidth = wordSpacing + letterSpacing - std::fmod(actualWidth - wordSpacing, letterSpacing);
924 }
925 LC_LOGRS_Debug::Log() << __LINE__925 << ": actualWidth: " << actualWidth;
926
927 letterPosition.x += actualWidth;
928
929 oneLine.addEntity(letterEntity);
930
931 // next letter position:
932 letterPosition += letterSpace;
933}
934
935RS_MText* RS_MText::createUpperLower(QString text, const RS_MTextData& data, const RS_Vector& position) {
936 auto* line = new RS_MText(nullptr, {
937 position,
938 4.0,
939 100.0,
940 RS_MTextData::VATop,
941 RS_MTextData::HALeft,
942 data.drawingDirection,
943 RS_MTextData::Exact,
944 1.0,
945 std::move(text),
946 data.style,
947 0.0,
948 RS2::Update
949 });
950 line->setLayer(nullptr);
951 line->setPen(RS_Pen(RS2::FlagInvalid));
952 line->calculateBorders();
953 return line;
954}
955
956/**
957 * Used internally by update() to add a text line created with
958 * default values and alignment to this text container.
959 *
960 * @param textLine The text line.
961 * @param lineCounter Line number.
962 *
963 * @return distance over the text base-line
964 */
965double RS_MText::updateAddLine(LC_TextLine* textLine, const int lineCounter) {
966 constexpr double ls = 5.0 / 3.0;
967
968 RS_DEBUGRS_Debug::instance()->print("RS_MText::updateAddLine: width: %f", textLine->getSize().x);
969
970 // textLine->forcedCalculateBorders();
971 // RS_DEBUG->print("RS_MText::updateAddLine: width 2: %f",
972 // textLine->getSize().x);
973
974 // Scale:
975 const double scale = m_data.height / 9.0;
976 textLine->scale(RS_Vector{0., 0.}, RS_Vector(scale, scale));
977
978 textLine->forcedCalculateBorders();
979
980 // Horizontal Align:
981 switch (m_data.halign) {
982 case RS_MTextData::HACenter:
983 textLine->move(RS_Vector{-0.5 * (textLine->getMin().x + textLine->getMax().x), 0.});
984 break;
985
986 case RS_MTextData::HARight:
987 textLine->move(RS_Vector{-textLine->getMax().x, 0.});
988 break;
989
990 default:
991 textLine->move(RS_Vector{-textLine->getMin().x, 0.});
992 break;
993 }
994
995 // Update actual text size (before rotating, after scaling!):
996 if (textLine->getSize().x > m_usedTextWidth) {
997 m_usedTextWidth = textLine->getSize().x;
998 }
999
1000 m_usedTextHeight += m_data.height * m_data.lineSpacingFactor * ls;
1001
1002 // Gets the distance over text base-line (before rotating, after scaling!):
1003 const double textTail = textLine->getMin().y;
1004
1005 // Move:
1006 textLine->move(m_data.insertionPoint + RS_Vector{0., -m_data.height * lineCounter * m_data.lineSpacingFactor * ls});
1007 // Rotate:
1008 // textLine->rotate(m_data.insertionPoint, m_data.angle);
1009
1010 textLine->setPen(RS_Pen(RS2::FlagInvalid));
1011 textLine->setLayer(nullptr);
1012 textLine->forcedCalculateBorders();
1013
1014 addEntity(textLine);
1015 return textTail;
1016}
1017
1018RS_Vector RS_MText::doGetNearestEndpoint(const RS_Vector& coord, double* dist, RS_Entity** entity) const {
1019 if (dist != nullptr) {
1020 *dist = m_data.insertionPoint.distanceTo(coord);
1021 }
1022 if (entity != nullptr) {
1023 *entity = const_cast<RS_MText*>(this);
1024 }
1025 return m_data.insertionPoint;
1026}
1027
1028RS_VectorSolutions RS_MText::getRefPoints() const {
1029 return {m_data.insertionPoint};
1030}
1031
1032RS_Vector RS_MText::doGetNearestRef(const RS_Vector& coord, double* dist) const {
1033 return RS_Entity::doGetNearestRef(coord, dist);
1034}
1035
1036RS_Vector RS_MText::doGetNearestSelectedRef(const RS_Vector& coord, double* dist) const {
1037 return RS_Entity::doGetNearestSelectedRef(coord, dist);
1038}
1039
1040void RS_MText::moveRef([[maybe_unused]] const RS_Vector& ref, const RS_Vector& offset) {
1041 move(offset);
1042}
1043
1044void RS_MText::moveSelectedRef([[maybe_unused]] const RS_Vector& ref, const RS_Vector& offset) {
1045 move(offset);
1046}
1047
1048void RS_MText::move(const RS_Vector& offset) {
1049 RS_EntityContainer::move(offset);
1050 m_data.insertionPoint.move(offset);
1051 // update();
1052 for (RS_Entity* e : std::as_const(*this)) {
1053 const auto line = dynamic_cast<LC_TextLine*>(e);
1054 if (line != nullptr) {
1055 line->moveBaseline(offset);
1056 }
1057 }
1058 forcedCalculateBorders();
1059}
1060
1061void RS_MText::rotate(const RS_Vector& center, const double angle) {
1062 RS_EntityContainer::rotate(center, angle);
1063 m_data.insertionPoint.rotate(center, angle);
1064
1065 // update();
1066 // calculateBorders();
1067
1068 for (RS_Entity* e : std::as_const(*this)) {
1069 const auto line = dynamic_cast<LC_TextLine*>(e);
1070 if (line != nullptr) {
1071 RS_Vector corner = line->getLeftBottomCorner();
1072 // corner.rotate(oldInsertionPoint, -m_data.angle);
1073 corner.rotate(center, angle);
1074 line->setLeftBottomCorner(corner);
1075 }
1076 }
1077
1078 m_data.angle = RS_Math::correctAngle(m_data.angle + angle);
1079
1080 rotateLinesRefs();
1081}
1082
1083// fixme - sand - check whether this function is actually used
1084void RS_MText::rotate(const RS_Vector& center, const RS_Vector& angleVector) {
1085 RS_EntityContainer::rotate(center, angleVector);
1086 m_data.insertionPoint.rotate(center, angleVector);
1087 m_data.angle = RS_Math::correctAngle(m_data.angle + angleVector.angle());
1088 /* RS_Vector lineRotationVector(angleVector.angle());
1089 for (RS_Entity *e: std::as_const(entities)) {
1090 auto line = dynamic_cast<LC_TextLine *> (e);
1091 if (line != nullptr) {
1092 RS_Vector corner = line->getLeftBottomCorner();
1093 corner.rotate(center, lineRotationVector);
1094 }
1095 }
1096 m_data.angle = RS_Math::correctAngle(m_data.angle + angleVector.angle());
1097 rotateLinesRefs();
1098 */
1099 update();
1100}
1101
1102void RS_MText::scale(const RS_Vector& center, const RS_Vector& factor) {
1103 m_data.insertionPoint.scale(center, factor);
1104 m_data.width *= factor.x;
1105 m_data.height *= factor.x;
1106 update();
1107}
1108
1109void RS_MText::mirror(const RS_Vector& axisPoint1, const RS_Vector& axisPoint2) {
1110 m_data.insertionPoint.mirror(axisPoint1, axisPoint2);
1111 // double ang = axisPoint1.angleTo(axisPoint2);
1112 const bool readable = RS_Math::isAngleReadable(m_data.angle);
1113
1114 RS_Vector vec = RS_Vector::polar(1.0, m_data.angle);
1115 vec.mirror(RS_Vector(0.0, 0.0), axisPoint2 - axisPoint1);
1116 m_data.angle = vec.angle();
1117
1118 bool corr = false;
1119 m_data.angle = RS_Math::makeAngleReadable(m_data.angle, readable, &corr);
1120
1121 if (corr) {
1122 if (m_data.halign == RS_MTextData::HALeft) {
1123 m_data.halign = RS_MTextData::HARight;
1124 }
1125 else if (m_data.halign == RS_MTextData::HARight) {
1126 m_data.halign = RS_MTextData::HALeft;
1127 }
1128 }
1129 else {
1130 if (m_data.valign == RS_MTextData::VATop) {
1131 m_data.valign = RS_MTextData::VABottom;
1132 }
1133 else if (m_data.valign == RS_MTextData::VABottom) {
1134 m_data.valign = RS_MTextData::VATop;
1135 }
1136 }
1137 update();
1138}
1139
1140bool RS_MText::hasEndpointsWithinWindow(const RS_Vector& /*v1*/, const RS_Vector& /*v2*/) const {
1141 return false;
1142}
1143
1144/**
1145 * Implementations must stretch the given range of the entity
1146 * by the given offset.
1147 */
1148void RS_MText::stretch(const RS_Vector& firstCorner, const RS_Vector& secondCorner, const RS_Vector& offset) {
1149 if (getMin().isInWindow(firstCorner, secondCorner) && getMax().isInWindow(firstCorner, secondCorner)) {
1150 move(offset);
1151 }
1152}
1153
1154/**
1155 * Dumps the point's m_data to stdout.
1156 */
1157std::ostream& operator<<(std::ostream& os, const RS_MText& p) {
1158 os << " Text: " << p.getData() << "\n";
1159 return os;
1160}
1161
1162#define DEBUG_LINE_POINTS_
1163
1164void RS_MText::draw(RS_Painter* painter) {
1165#ifdef DEBUG_LINE_POINTS
1166 painter->drawRect(view->toGui(getMin()), view->toGui(getMax()));
1167#endif
1168
1169 const bool drawAsDraft = painter->isTextLineNotRenderable(getHeight());
1170 if (drawAsDraft) {
1171 drawDraft(painter);
1172 return;
1173 }
1174
1175 for (RS_Entity* entity : std::as_const(*this)) {
1176 painter->drawAsChild(entity);
1177
1178#ifdef DEBUG_LINE_POINTS
1179 auto line = dynamic_cast<LC_TextLine*>(entity); if (line != nullptr) {
1180 painter->drawRect(view->toGui(line->getBaselineStart()) - 2, view->toGui(line->getBaselineStart()) + 2);
1181 painter->drawRect(view->toGui(line->getLeftBottomCorner()) - 4, view->toGui(line->getLeftBottomCorner()) + 4);
1182 }
1183#endif
1184 }
1185}
1186
1187void RS_MText::drawDraft(RS_Painter* painter) {
1188#ifdef DEBUG_LINE_POINTS
1189 painter->drawRect(painter->toGui(getMin()), painter->toGui(getMax()));
1190#endif
1191 for (RS_Entity* entity : std::as_const(*this)) {
1192 const auto line = dynamic_cast<LC_TextLine*>(entity);
1193 if (line != nullptr && line->count() > 0) {
1194 painter->drawLineWCS(line->getBaselineStart(), line->getBaselineEnd());
1195 }
1196 }
1197}