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 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| 11 | |
| 12 | |
| 13 | |
| 14 | |
| 15 | |
| 16 | |
| 17 | |
| 18 | |
| 19 | |
| 20 | |
| 21 | |
| 22 | |
| 23 | |
| 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 | |
| 41 | |
| 42 | |
| 43 | |
| 44 | |
| 45 | |
| 46 | |
| 47 | std::vector<int> |
| 48 | RS_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 | |
|
| |
| 57 | |
| 58 | |
| 59 | |
| 60 | |
| 61 | |
| 62 | |
| 63 | |
| 64 | constexpr int LVL_UNKNOWN = -1; |
| 65 | std::vector<int> level(n, LVL_UNKNOWN); |
| 66 | for (int i = 0; i < n; ++i) { |
| |
| 5 | | Loop condition is true. Entering loop body | |
|
| |
| 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 | |
| 78 | |
| 79 | |
| 80 | |
| 81 | level[i] = (baseLevel == 1) ? 2 : 2; |
| 82 | break; |
| 83 | default: |
| 84 | |
| 85 | |
| 86 | level[i] = LVL_UNKNOWN; |
| 87 | break; |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | |
| 92 | |
| 93 | |
| 94 | |
| 95 | |
| 96 | |
| 97 | |
| 98 | { |
| 99 | |
| 100 | |
| 101 | |
| 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 | |
| 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 | |
| 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); |
| |
| 161 | if (closer != 0) { |
| 162 | if (stack.size() < 63) { |
| 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); |
| 170 | break; |
| 171 | } |
| 172 | } |
| 173 | } |
| 174 | } |
| 175 | const int embeddingLevel = baseLevel; |
| 176 | for (const auto &p : pairs) { |
| 177 | |
| 178 | int strongInside = LVL_UNKNOWN; |
| 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 | |
| 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; |
| 215 | } |
| 216 | level[p.open] = bracketLevel; |
| 217 | level[p.close] = bracketLevel; |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | |
| 222 | |
| 223 | |
| 224 | |
| 225 | |
| 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; |
| 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 | |
| 258 | |
| 259 | result.reserve(n); |
| 260 | for (int k = 0; k < n; ++k) |
| 261 | result.push_back(k); |
| 262 | |
| 263 | |
| 264 | int maxLevel = 0; |
| 265 | for (int k = 0; k < n; ++k) { |
| 266 | if (level[k] > maxLevel) |
| 267 | maxLevel = level[k]; |
| 268 | } |
| 269 | |
| 270 | |
| 271 | |
| 272 | |
| 273 | |
| 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 | |
| 290 | RS_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 | |
| 311 | const RS_Vector& RS_MText::LC_TextLine::getTextSize() const { |
| 312 | return m_textSize; |
| 313 | } |
| 314 | |
| 315 | void RS_MText::LC_TextLine::setTextSize(const RS_Vector& v) { |
| 316 | m_textSize = v; |
| 317 | } |
| 318 | |
| 319 | const RS_Vector& RS_MText::LC_TextLine::getLeftBottomCorner() const { |
| 320 | return m_leftBottomCorner; |
| 321 | } |
| 322 | |
| 323 | void RS_MText::LC_TextLine::setLeftBottomCorner(const RS_Vector& v) { |
| 324 | m_leftBottomCorner = v; |
| 325 | } |
| 326 | |
| 327 | const RS_Vector& RS_MText::LC_TextLine::getBaselineStart() const { |
| 328 | return m_baselineStart; |
| 329 | } |
| 330 | |
| 331 | void RS_MText::LC_TextLine::setBaselineStart(const RS_Vector& v) { |
| 332 | m_baselineStart = v; |
| 333 | } |
| 334 | |
| 335 | const RS_Vector& RS_MText::LC_TextLine::getBaselineEnd() const { |
| 336 | return m_baselineEnd; |
| 337 | } |
| 338 | |
| 339 | void RS_MText::LC_TextLine::setBaselineEnd(const RS_Vector& v) { |
| 340 | m_baselineEnd = v; |
| 341 | } |
| 342 | |
| 343 | void 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 | |
| 349 | RS_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 | |
| 358 | std::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 | |
| 367 | |
| 368 | RS_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 | |
| 373 | RS_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 | |
| 381 | |
| 382 | class RS_MTextProxy : public RS_EntityContainer { |
| 383 | public: |
| 384 | explicit RS_MTextProxy(const RS_MText& parent) : RS_EntityContainer(parent) { |
| 385 | } |
| 386 | }; |
| 387 | |
| 388 | RS_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 | |
| 405 | |
| 406 | |
| 407 | void RS_MText::setText(QString t) { |
| 408 | m_data.text = std::move(t); |
| 409 | |
| 410 | |
| 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 | |
| 429 | void 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) { |
| 435 | update(); |
| 436 | } |
| 437 | } |
| 438 | |
| 439 | |
| 440 | |
| 441 | |
| 442 | |
| 443 | |
| 444 | int 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 | |
| 484 | |
| 485 | |
| 486 | |
| 487 | void 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 | |
| 522 | |
| 523 | int 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 | |
| 531 | |
| 532 | |
| 533 | |
| 534 | void RS_MText::update() { |
| 535 | RS_DEBUG->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_FONTLIST->requestFont(m_data.style)}; |
| 546 | if (nullptr == font) { |
| 547 | return; |
| 548 | } |
| 549 | |
| 550 | |
| 551 | |
| 552 | |
| 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 | |
| 560 | |
| 561 | auto oneLine{new LC_TextLine(this)}; |
| 562 | |
| 563 | |
| 564 | |
| 565 | |
| 566 | |
| 567 | |
| 568 | |
| 569 | |
| 570 | |
| 571 | |
| 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 | |
| 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 | |
| 590 | |
| 591 | |
| 592 | |
| 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 | |
| 601 | closeLine(); |
| 602 | break; |
| 603 | |
| 604 | case 0x20: |
| 605 | |
| 606 | segments.push_back( |
| 607 | {LC_BidiSegment::Space, QChar(' '), nullptr, spaceWidth, {}, {}}); |
| 608 | break; |
| 609 | |
| 610 | case 0x5C: { |
| 611 | bool handled{false}; |
| 612 | |
| 613 | ++i; |
| 614 | if (static_cast<int>(m_data.text.length()) <= i) { |
| 615 | continue; |
| 616 | } |
| 617 | |
| 618 | |
| 619 | |
| 620 | |
| 621 | |
| 622 | |
| 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 | |
| 633 | |
| 634 | |
| 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_FONTLIST->requestFont(fontName); |
| 646 | if (requestedFont != nullptr) { |
| 647 | font = requestedFont; |
| 648 | } |
| 649 | else { |
| 650 | font = RS_FONTLIST->requestFont("standard"); |
| 651 | } |
| 652 | i = j; |
| 653 | } |
| 654 | continue; |
| 655 | } |
| 656 | |
| 657 | case 'S': { |
| 658 | QString upperText; |
| 659 | QString lowerText; |
| 660 | |
| 661 | |
| 662 | ++i; |
| 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 | |
| 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 | } |
| 690 | |
| 691 | default: |
| 692 | --i; |
| 693 | break; |
| 694 | } |
| 695 | |
| 696 | if (handled) { |
| 697 | break; |
| 698 | } |
| 699 | } |
| 700 | |
| 701 | |
| 702 | |
| 703 | default: { |
| 704 | |
| 705 | segments.push_back( |
| 706 | {LC_BidiSegment::Char, m_data.text.at(i), font, 0.0, {}, {}}); |
| 707 | break; |
| 708 | } |
| 709 | } |
| 710 | } |
| 711 | |
| 712 | |
| 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_DEBUG->print("RS_MText::update: OK"); |
| 722 | } |
| 723 | |
| 724 | |
| 725 | |
| 726 | |
| 727 | |
| 728 | |
| 729 | |
| 730 | |
| 731 | void 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 | |
| 740 | |
| 741 | |
| 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 | |
| 759 | |
| 760 | |
| 761 | |
| 762 | |
| 763 | |
| 764 | |
| 765 | |
| 766 | |
| 767 | |
| 768 | |
| 769 | |
| 770 | |
| 771 | |
| 772 | |
| 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 | |
| 790 | segFont = RS_FONTLIST->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 | |
| 828 | void RS_MText::alignVertically() { |
| 829 | |
| 830 | switch (m_data.valign) { |
| 831 | case RS_MTextData::VATop: { |
| 832 | |
| 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 | |
| 841 | RS_EntityContainer::move({0., m_usedTextHeight}); |
| 842 | break; |
| 843 | } |
| 844 | default: LC_ERR << __func__ << "(): line " << __LINE__ << ": 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 | |
| 863 | void 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_2); |
| 871 | |
| 872 | RS_Vector baselineStart = corner + shiftVector; |
| 873 | RS_Vector baselineEnd = corner + shiftVector + RS_Vector::polar(size.x, m_data.angle); |
| 874 | |
| 875 | |
| 876 | line->setBaselineStart(baselineStart); |
| 877 | |
| 878 | |
| 879 | line->setBaselineEnd(baselineEnd); |
| 880 | } |
| 881 | } |
| 882 | } |
| 883 | |
| 884 | |
| 885 | |
| 886 | |
| 887 | |
| 888 | |
| 889 | |
| 890 | |
| 891 | |
| 892 | |
| 893 | |
| 894 | |
| 895 | |
| 896 | |
| 897 | |
| 898 | |
| 899 | |
| 900 | void 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_DEBUG->print("RS_MText::update: missing font for letter( %s ), replaced " "it with QChar(0xfffd)", qPrintable(letterText)); |
| 904 | letterText = QChar(0xfffd); |
| 905 | } |
| 906 | |
| 907 | LC_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 | |
| 919 | double actualWidth = letterEntity->getMax().x - letterEntity->getMin().x; |
| 920 | if (actualWidth >= font.getWordSpacing() + RS_TOLERANCE) { |
| 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_LOG << __LINE__ << ": actualWidth: " << actualWidth; |
| 926 | |
| 927 | letterPosition.x += actualWidth; |
| 928 | |
| 929 | oneLine.addEntity(letterEntity); |
| 930 | |
| 931 | |
| 932 | letterPosition += letterSpace; |
| 933 | } |
| 934 | |
| 935 | RS_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 | |
| 958 | |
| 959 | |
| 960 | |
| 961 | |
| 962 | |
| 963 | |
| 964 | |
| 965 | double RS_MText::updateAddLine(LC_TextLine* textLine, const int lineCounter) { |
| 966 | constexpr double ls = 5.0 / 3.0; |
| 967 | |
| 968 | RS_DEBUG->print("RS_MText::updateAddLine: width: %f", textLine->getSize().x); |
| 969 | |
| 970 | |
| 971 | |
| 972 | |
| 973 | |
| 974 | |
| 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 | |
| 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 | |
| 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 | |
| 1003 | const double textTail = textLine->getMin().y; |
| 1004 | |
| 1005 | |
| 1006 | textLine->move(m_data.insertionPoint + RS_Vector{0., -m_data.height * lineCounter * m_data.lineSpacingFactor * ls}); |
| 1007 | |
| 1008 | |
| 1009 | |
| 1010 | textLine->setPen(RS_Pen(RS2::FlagInvalid)); |
| 1011 | textLine->setLayer(nullptr); |
| 1012 | textLine->forcedCalculateBorders(); |
| 1013 | |
| 1014 | addEntity(textLine); |
| 1015 | return textTail; |
| 1016 | } |
| 1017 | |
| 1018 | RS_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 | |
| 1028 | RS_VectorSolutions RS_MText::getRefPoints() const { |
| 1029 | return {m_data.insertionPoint}; |
| 1030 | } |
| 1031 | |
| 1032 | RS_Vector RS_MText::doGetNearestRef(const RS_Vector& coord, double* dist) const { |
| 1033 | return RS_Entity::doGetNearestRef(coord, dist); |
| 1034 | } |
| 1035 | |
| 1036 | RS_Vector RS_MText::doGetNearestSelectedRef(const RS_Vector& coord, double* dist) const { |
| 1037 | return RS_Entity::doGetNearestSelectedRef(coord, dist); |
| 1038 | } |
| 1039 | |
| 1040 | void RS_MText::moveRef([[maybe_unused]] const RS_Vector& ref, const RS_Vector& offset) { |
| 1041 | move(offset); |
| 1042 | } |
| 1043 | |
| 1044 | void RS_MText::moveSelectedRef([[maybe_unused]] const RS_Vector& ref, const RS_Vector& offset) { |
| 1045 | move(offset); |
| 1046 | } |
| 1047 | |
| 1048 | void RS_MText::move(const RS_Vector& offset) { |
| 1049 | RS_EntityContainer::move(offset); |
| 1050 | m_data.insertionPoint.move(offset); |
| 1051 | |
| 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 | |
| 1061 | void 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 | |
| 1066 | |
| 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 | |
| 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 | |
| 1084 | void 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 | |
| 1089 | |
| 1090 | |
| 1091 | |
| 1092 | |
| 1093 | |
| 1094 | |
| 1095 | |
| 1096 | |
| 1097 | |
| 1098 | |
| 1099 | update(); |
| 1100 | } |
| 1101 | |
| 1102 | void 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 | |
| 1109 | void RS_MText::mirror(const RS_Vector& axisPoint1, const RS_Vector& axisPoint2) { |
| 1110 | m_data.insertionPoint.mirror(axisPoint1, axisPoint2); |
| 1111 | |
| 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 | |
| 1140 | bool RS_MText::hasEndpointsWithinWindow(const RS_Vector& , const RS_Vector& ) const { |
| 1141 | return false; |
| 1142 | } |
| 1143 | |
| 1144 | |
| 1145 | |
| 1146 | |
| 1147 | |
| 1148 | void 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 | |
| 1156 | |
| 1157 | std::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 | |
| 1164 | void 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 | |
| 1187 | void 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 | } |