Fix copy as image for chinese#15
Conversation
This properly considers the width of the base character in a grapheme (it ignores the effect of any combining diacriticals) but DOES account for those Unicode codepoints that are NOT in the basic (first) Unicode Multi-plane. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
…dths For the use made of the widths concerned it makes more sense to use it rather than the width of a space or 'W' character - and is less likely to cause a seg. fault for fonts that do not have the selected character; Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
The code concerned is very spammy so it should only be switched on when needed. Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
src/TTextEdit.cpp
Outdated
| } else if (first.isLowSurrogate() && second.isHighSurrogate()) { | ||
| } | ||
|
|
||
| // Although this has been coded for it is not what I expect to see - SlySven: |
There was a problem hiding this comment.
The Q_UNLIKELY says enough that this is not to be expected, so the comment doesn't add value - let's have less text the programmer needs to see. We've got thousands of lines of code already!
There was a problem hiding this comment.
Someone else had put those lines in, I don't think they are necessary, but I am not certain enough that I want to remove them...
There was a problem hiding this comment.
... and putting my name there indicates who should be contacted if it ever is found.
There was a problem hiding this comment.
That's not how people will see it and I don't think it adds value. Let's keep code minimal with only the necessary explanations.
There was a problem hiding this comment.
Okay, I will remove comment (and revise the debug text).
|
|
||
| // Returns the index into the relevant TBuffer::lineBuffer of the FIRST QChar | ||
| // of the grapheme under the mouse - it ALSO returns zero (which will probably | ||
| // NOT be a valid index) if there is no valid index to return; it might be |
There was a problem hiding this comment.
Zero is the first character in the line, that is a valid index.
There was a problem hiding this comment.
Not if the line is an empty one! We do not include the EOL character and we do get lines with NO content - such that using TBuffer::lineBuffer.at(row).at(column) on them is NOT valid and will seg. fault if tried... 😢
| if (lineNumber >= 0 && lineNumber < mpBuffer->lineBuffer.size()) { | ||
| // Line number is (should be) within range of lines in the | ||
| // TBuffer::lineBuffer - might need to check that this still works after | ||
| // that buffer has reached the limit when it starts to have the |
There was a problem hiding this comment.
No - though I am aware that we do have an open issue about things breaking when the buffer has reached the set limit and has started to truncate itself (I haven't been able to find it though which is a pain).
I am not certain at this time how that process proceeds and with so many balls in the air right now I haven't taken the time to try and get my head around it - I'm just hoping it isn't going to surprise me/us... 🙏
There was a problem hiding this comment.
... though I am aware that we do have an open issue about things breaking when the buffer has reached the set limit and has started to truncate itself ...
There is something screwy going on in that situation - if the display is NOT split the on-screen display seems to update a few lines at the bottom but then redraws the same lines and you do not see the new content that - if the TConsole is split does scroll up in the lower pane. The bottom line is that this new code does not seem to be any more broken by this than the previous code - OTOH there is definitely something buggy there but what on earth it is is not clear to me at the present... for reference I used a call of setConsoleBufferSize(200,10) to provoke that bug in the main console.
|
|
||
| // Technically this copies whole lines into the image even if the selection does | ||
| // not start at the beginning of the first line or end at the last grapheme on | ||
| // the last line. |
There was a problem hiding this comment.
Not a useful code comment (good piece of trivia, though 😉)
There was a problem hiding this comment.
It is useful - and it points out a disparity in the behaviour compared to what might be anticipated from assigning values to mPA::x and mPB::x - as I said technically the disparity makes sense - as forming a rectangular picture but blanking off the parts of the first and last line outside of the selection would be even harder to do right for the image form than it was for the text/HTML case - and remember how mixed the feelings about that were!
| characterIndex -= 13; | ||
| } | ||
| characterIndex = std::max(characterIndex, 0); | ||
| return characterIndex; |
There was a problem hiding this comment.
Why was all of this deleted? This keeps the old behaviour of still returning a theoretical X position for when you're out of the buffer range. I don't know how the code will behave when it's suddenly getting 0 now, have you tested and verified all of this?
There was a problem hiding this comment.
If the lineNumber argument is out of range then there is NO useful value to return! The "BufferX" in the method name is not a spatial characteristic but an array index - note that the value I return is based on indexOfChar which is the first QChar in the particular grapheme under the mouse cursor on the relevant line {perhaps that should have been indexOfQChar} and it would not be possible to derive a meaningful value in the absence of any graphemes to draw and estimate their width from.
Perhaps that is not entirely fair: it might be possible to estimate the number of average width characters - given that that is how much space we now assign for a single, "normal" width grapheme - but to than take that number and to use it to index into a random line of text in the TBuffer::lineBuffer would be nonsensical even if it did not cause a seg. fault by trying to access beyond the end of that random line.
Thus in the situation when it is not possible to access the buffer either we could return -1 as a sentinel value as I hypothesize or we return 0 (as we do currently) and rely on the caller to check that lineNumber of the buffer will support the index value returned...
There was a problem hiding this comment.
Ok but that's how the rest of the code works right now and I don't want to refactor it entirely and or introduce a behaviour change. It's one day until feature freeze, now is not the time
There was a problem hiding this comment.
I've taken a closer look at b3f3761 and what you are saying about "that is how the rest of the code works right now" seems to confirm to me that that is why the existing code doesn't work for non-BMP and combining diacriticals precisely because there is not a 1:1 relationship between how far across from the left margin the mouse X position is and the index into the QChars that form the QString that is the textual data for each line of Mud text. By 1:1 I mean that for a certain number of pixels from the left side the index of the QChar that is at that number of pixels is a fixed and invariant value. That is what the previous code assumed and it is only true for BMP characters without any diacriticals (and without any non-printing Unicode codepoints such as Variation Selectors, Joiners, etc.)
The code I am proposing is good enough provided that we assume that the text we present (barring tabs) can be done in a duo-spaced (each glyph takes up either one or two "spaces") manner. {As it happens I have prototyped code that does things in a slower and more involved manner that actually recorded the width each grapheme took when painted on screen and looked that up when it need to work out where the mouse movement put the cursor - which meant it could handle Qt using glyphs from different fonts as needed to display the text - which this code will not cope with too well if the individual characters deviate too much from the "average" width.}
Saying "it's is one day until feature freeze" is not helpful - either we want code that works for a non-English user base or we put forward a product that has known miss-features.
I am sorry if I did not point this out more strongly when the 4.0.0. release was recently discussed but as I said at the time I did not feel we had everything sorted yet and I pointed out the limits on my current attention to the project and the reason why. It was only when I started looking at your PR - that this one is hacking away at - that I realised that I hadn't sorted out the selection and highlighting code as completely as I had intended. OTOH This will, I believe, be good enough for Western and Chinese languages cases.
There was a problem hiding this comment.
That is what the previous code assumed and it is only true for BMP characters without any diacriticals (and without any non-printing Unicode codepoints such as Variation Selectors, Joiners, etc.)
I'm sorry but I don't follow what are you talking about. We've ruled out emojis as not a 4.0 requirement, so what usecases are you looking to fix here?
My original PR fixes it for Chinese, all the other languages we know of work fine. As far as I know there are no Vietnamese MUDs so that is not a concern. Is the concern you're having practical or theoretical?
Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
|
The macOs Travis CI builds errored & failed (one of each) but the Linux builds all succeeded. For some reason though - I do not seem to have the permissions to re-request the unsuccessful CI jobs to be restarted. 😕 |
…rocessing (Mudlet#8571) <!-- Keep the title short & concise so anyone non-technical can understand it, the title appears in PTB changelogs --> #### Brief overview of PR changes/additions Fix: heap-use-after-free when cleanup runs during alias/trigger/key processing #### Motivation for adding to Mudlet Fixes crash when running Mudlet#8559 (comment) benchmark on Linux. #### Other info (issues closed, discussion etc) ==617553==ERROR: AddressSanitizer: heap-use-after-free on address 0x51200086e6d0 at pc 0x589b650367f6 bp 0x7ffc44dbc700 sp 0x7ffc44dbc6f8 READ of size 8 at 0x51200086e6d0 thread T0 #0 0x589b650367f5 in Tree<TAlias>::isActive() const (/home/vadi/Programs/Mudlet/build/src/mudlet+0xe8a7f5) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #1 0x589b65d81408 in TAlias::match(QString const&) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bd5408) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #2 0x589b6560c156 in AliasUnit::processDataStream(QString const&) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1460156) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #3 0x589b65c872b4 in Host::send(QString, bool, bool) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1adb2b4) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #4 0x589b65d96517 in TCommandLine::enterCommand(QKeyEvent*) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bea517) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #5 0x589b65d93095 in TCommandLine::event(QEvent*) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1be7095) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #6 0x7ac668391c8a in QApplicationPrivate::notify_helper(QObject*, QEvent*) /home/qt/work/qt/qtbase/src/widgets/kernel/qapplication.cpp:3307:31 #7 0x7ac66839b2f0 in QApplication::notify(QObject*, QEvent*) /home/qt/work/qt/qtbase/src/widgets/kernel/qapplication.cpp:2725:39 #8 0x7ac668b83f7f in QCoreApplication::notifyInternal2(QObject*, QEvent*) /home/qt/work/qt/qtbase/src/corelib/kernel/qcoreapplication.cpp:1109:24 #9 0x7ac66840cc0b in QWidgetWindow::event(QEvent*) /home/qt/work/qt/qtbase/src/widgets/kernel/qwidgetwindow.cpp:285:23 #10 0x7ac668391c8a in QApplicationPrivate::notify_helper(QObject*, QEvent*) /home/qt/work/qt/qtbase/src/widgets/kernel/qapplication.cpp:3307:31 #11 0x7ac668b83f7f in QCoreApplication::notifyInternal2(QObject*, QEvent*) /home/qt/work/qt/qtbase/src/corelib/kernel/qcoreapplication.cpp:1109:24 #12 0x7ac6677ee8e2 in QGuiApplicationPrivate::processKeyEvent(QWindowSystemInterfacePrivate::KeyEvent*) /home/qt/work/qt/qtbase/src/gui/kernel/qguiapplication.cpp:2609:46 #13 0x7ac655cf9a04 in QIBusPlatformInputContext::filterEventFinished(QDBusPendingCallWatcher*) /home/qt/work/qt/qtbase/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp:523:57 #14 0x7ac668be8b74 in QtPrivate::QSlotObjectBase::call(QObject*, void**) /home/qt/work/qt/qtbase/src/corelib/kernel/qobjectdefs_impl.h:461:57 #15 0x7ac668be8b74 in void doActivate<false>(QObject*, int, void**) /home/qt/work/qt/qtbase/src/corelib/kernel/qobject.cpp:4255:30 #16 0x7ac6671e5142 in void QMetaObject::activate<void, QDBusPendingCallWatcher*>(QObject*, QMetaObject const*, int, void*, QDBusPendingCallWatcher* const&) /home/qt/work/qt/qtbase/src/corelib/kernel/qobjectdefs.h:319:17 #17 0x7ac6671e5142 in QDBusPendingCallWatcher::finished(QDBusPendingCallWatcher*) /home/qt/work/qt/qtbase_build/src/dbus/DBus_autogen/include/moc_qdbuspendingcall.cpp:137:32 #18 0x7ac668bdd56b in QObject::event(QEvent*) /home/qt/work/qt/qtbase/src/corelib/kernel/qobject.cpp:1411:31 #19 0x7ac668391c8a in QApplicationPrivate::notify_helper(QObject*, QEvent*) /home/qt/work/qt/qtbase/src/widgets/kernel/qapplication.cpp:3307:31 #20 0x7ac668b83f7f in QCoreApplication::notifyInternal2(QObject*, QEvent*) /home/qt/work/qt/qtbase/src/corelib/kernel/qcoreapplication.cpp:1109:24 #21 0x7ac668b879e4 in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) /home/qt/work/qt/qtbase/src/corelib/kernel/qcoreapplication.cpp:1904:36 #22 0x7ac668e7d416 in postEventSourceDispatch /home/qt/work/qt/qtbase/src/corelib/kernel/qeventdispatcher_glib.cpp:246:39 #23 0x7ac6667145c4 (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x5d5c4) (BuildId: 1eb6131419edb83b2178b682829a6913cf682d75) #24 0x7ac666773736 (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0xbc736) (BuildId: 1eb6131419edb83b2178b682829a6913cf682d75) #25 0x7ac666713a62 in g_main_context_iteration (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x5ca62) (BuildId: 1eb6131419edb83b2178b682829a6913cf682d75) #26 0x7ac668e7caad in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) /home/qt/work/qt/qtbase/src/corelib/kernel/qeventdispatcher_glib.cpp:399:43 #27 0x7ac668b9002a in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) /home/qt/work/qt/qtbase/src/corelib/kernel/qeventloop.cpp:186:22 #28 0x7ac668b8ba59 in QCoreApplication::exec() /home/qt/work/qt/qtbase/src/corelib/kernel/qcoreapplication.cpp:1452:36 #29 0x589b64ab0675 in main (/home/vadi/Programs/Mudlet/build/src/mudlet+0x904675) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #30 0x7ac66602a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 #31 0x7ac66602a28a in __libc_start_main csu/../csu/libc-start.c:360:3 #32 0x589b649c1d04 in _start (/home/vadi/Programs/Mudlet/build/src/mudlet+0x815d04) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) 0x51200086e6d0 is located 16 bytes inside of 296-byte region [0x51200086e6c0,0x51200086e7e8) freed by thread T0 here: #0 0x589b64a9b9f1 in operator delete(void*) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x8ef9f1) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #1 0x589b65d80711 in TAlias::~TAlias() (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bd4711) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #2 0x589b6560eefc in AliasUnit::doCleanup() (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1462efc) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #3 0x589b65c8e88d in Host::incomingStreamProcessor(QString const&, int) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1ae288d) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #4 0x589b651b73b5 in TMainConsole::runTriggers(int) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x100b3b5) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #5 0x589b64e39c4b in TBuffer::commitLine(char, unsigned long&) (/home/vadi/Programs/Mudlet/build/src/mudlet+0xc8dc4b) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #6 0x589b64e28d4e in TBuffer::translateToPlainText(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>&, bool) (/home/vadi/Programs/Mudlet/build/src/mudlet+0xc7cd4e) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #7 0x589b651b638d in TMainConsole::printOnDisplay(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>&, bool) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x100a38d) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #8 0x589b64f9ed5b in TLuaInterpreter::feedTriggers(lua_State*) (/home/vadi/Programs/Mudlet/build/src/mudlet+0xdf2d5b) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #9 0x7ac66942ffa0 in luaD_precall /build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:320:10 #10 0x7ac66943ad7a in luaV_execute /build/lua5.1-rMDsVj/lua5.1-5.1.5/src/lvm.c:591:17 #11 0x7ac66942e96c in luaD_call /build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:378:5 #12 0x7ac66942af70 in luaD_rawrunprotected /build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:116:3 #13 0x7ac66942bb94 in luaD_pcall /build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:464:12 #14 0x7ac66942bce0 in lua_pcall /build/lua5.1-rMDsVj/lua5.1-5.1.5/src/lapi.c:821:12 #15 0x589b64fd65f1 in TLuaInterpreter::call(QString const&, QString const&, bool) (/home/vadi/Programs/Mudlet/build/src/mudlet+0xe2a5f1) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #16 0x589b65d84d31 in TAlias::execute() (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bd8d31) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #17 0x589b65d84577 in TAlias::match(QString const&) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bd8577) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #18 0x589b6560c156 in AliasUnit::processDataStream(QString const&) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1460156) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #19 0x589b65c872b4 in Host::send(QString, bool, bool) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1adb2b4) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #20 0x589b65d96517 in TCommandLine::enterCommand(QKeyEvent*) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bea517) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #21 0x589b65d93095 in TCommandLine::event(QEvent*) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1be7095) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #22 0x7ac668391c8a in QApplicationPrivate::notify_helper(QObject*, QEvent*) /home/qt/work/qt/qtbase/src/widgets/kernel/qapplication.cpp:3307:31 previously allocated by thread T0 here: #0 0x589b64a9b171 in operator new(unsigned long) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x8ef171) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #1 0x589b6500f595 in TLuaInterpreter::startTempAlias(QString const&, QString const&) (/home/vadi/Programs/Mudlet/build/src/mudlet+0xe63595) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #2 0x589b6512c41d in TLuaInterpreter::tempAlias(lua_State*) (/home/vadi/Programs/Mudlet/build/src/mudlet+0xf8041d) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #3 0x7ac66942ffa0 in luaD_precall /build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:320:10 #4 0x7ac66943ad7a in luaV_execute /build/lua5.1-rMDsVj/lua5.1-5.1.5/src/lvm.c:591:17 #5 0x7ac66942e96c in luaD_call /build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:378:5 #6 0x7ac66942af70 in luaD_rawrunprotected /build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:116:3 #7 0x7ac66942bb94 in luaD_pcall /build/lua5.1-rMDsVj/lua5.1-5.1.5/src/ldo.c:464:12 #8 0x7ac66942bce0 in lua_pcall /build/lua5.1-rMDsVj/lua5.1-5.1.5/src/lapi.c:821:12 #9 0x589b64fd65f1 in TLuaInterpreter::call(QString const&, QString const&, bool) (/home/vadi/Programs/Mudlet/build/src/mudlet+0xe2a5f1) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #10 0x589b65d84d31 in TAlias::execute() (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bd8d31) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #11 0x589b65d84577 in TAlias::match(QString const&) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bd8577) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #12 0x589b6560c156 in AliasUnit::processDataStream(QString const&) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1460156) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #13 0x589b65c872b4 in Host::send(QString, bool, bool) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1adb2b4) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #14 0x589b65d96517 in TCommandLine::enterCommand(QKeyEvent*) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1bea517) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #15 0x589b65d93095 in TCommandLine::event(QEvent*) (/home/vadi/Programs/Mudlet/build/src/mudlet+0x1be7095) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) #16 0x7ac668391c8a in QApplicationPrivate::notify_helper(QObject*, QEvent*) /home/qt/work/qt/qtbase/src/widgets/kernel/qapplication.cpp:3307:31 SUMMARY: AddressSanitizer: heap-use-after-free (/home/vadi/Programs/Mudlet/build/src/mudlet+0xe8a7f5) (BuildId: c98a5e4208b6daa52aa1b083c4ee6c4ab4552cc4) in Tree<TAlias>::isActive() const Shadow bytes around the buggy address: 0x51200086e400: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x51200086e480: fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa 0x51200086e500: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd 0x51200086e580: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x51200086e600: fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa =>0x51200086e680: fa fa fa fa fa fa fa fa fd fd[fd]fd fd fd fd fd 0x51200086e700: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x51200086e780: fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa 0x51200086e800: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd 0x51200086e880: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x51200086e900: fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==617553==ABORTING Co-authored-by: Vadim Peretokin <vadi2@users.noreply.github.com>

This is a collection of 5 commits that better handles the text that makes up each line of text in the
TBuffer::lineBufferspecifically they iterate through the text on a grapheme by grapheme basis and considered the effective width of the glyphs from the passing of the first Unicode codepoint of the grapheme into thegetGraphemeWidth(ucs4)method. This means that non-BMP characters and combining diacriticals are now correctly accounted for - and stops the production of:error messages when the individual surrogates were previously pushed into that method and:
when combining diacriticals were so pushed...!
It also switches to using
QFontMetrics::averageCharWidth()instead of trying to measure the width of semi-randomly chosenQCharsi.e. space and 'W' withQFontMetrics::width(QChar)which has been declared obsolete and returns a potentially seg. faulting0in some situations that we have been encountering - see Mudlet#2391 .