Expose convertCharset convenience function to controllers - #13772
christophehenry wants to merge 3 commits into
Conversation
|
I think design-wise, it would make most sense to just return a QByteArray (which is automatically a JS TypedArray), since if you're converting into a different format, you'll likely want to send out the bytes soon after without manipulating them more (at least in terms of characters). Also putting it back into a QString would either convert it back to UTF-16 or create a (likely invalid, UB causing) string. |
Isn't what I'm doing here?
I don't understand… Where am I doing that? |
whoops, yeah, that assumption was based on the |
| icu::UnicodeString unicodeString; | ||
| UErrorCode errorCode; | ||
| UConverter* latin9Converter = ucnv_open(targetCharset.toLocal8Bit().data(), &errorCode); | ||
|
|
||
| if (!U_FAILURE(errorCode)) { | ||
| ucnv_close(latin9Converter); | ||
| return QJSValue::UndefinedValue; | ||
| } | ||
|
|
||
| char* result = nullptr; | ||
| ucnv_fromUChars(latin9Converter, | ||
| result, | ||
| 0, | ||
| &value.data()->unicode(), | ||
| value.length(), | ||
| &errorCode); | ||
| ucnv_close(latin9Converter); |
There was a problem hiding this comment.
Qt has a wrapper for all this. I think it should be:
| icu::UnicodeString unicodeString; | |
| UErrorCode errorCode; | |
| UConverter* latin9Converter = ucnv_open(targetCharset.toLocal8Bit().data(), &errorCode); | |
| if (!U_FAILURE(errorCode)) { | |
| ucnv_close(latin9Converter); | |
| return QJSValue::UndefinedValue; | |
| } | |
| char* result = nullptr; | |
| ucnv_fromUChars(latin9Converter, | |
| result, | |
| 0, | |
| &value.data()->unicode(), | |
| value.length(), | |
| &errorCode); | |
| ucnv_close(latin9Converter); | |
| QTextCodec* codec = QTextCodec::codecForName(targetCharset); | |
| if (!codec) { | |
| return QJSValue::UndefinedValue; | |
| } | |
| QByteArray result = codec->fromUnicode(value); |
(Not tested)
There was a problem hiding this comment.
Unfortunately this nice API was replaced in Qt6 by QStringConverter, which supports much less codecs. The old API is still available in the "Qt 5 Core Compat module" for Qt6, but I guess this is nothing that should be used in new code.
There was a problem hiding this comment.
Mmm interesting.
In Qt6 there is a common base class for QStringConverterBase for QStringConverter and QTextCodec
https://github.com/qt/qtbase/blob/30d90b4ccad83ab1f23dab7cd72b7e228c299895/src/corelib/text/qstringconverter.cpp#L1772
And it is also using ucnv under the hood:
https://github.com/qt/qtbase/blob/30d90b4ccad83ab1f23dab7cd72b7e228c299895/src/corelib/text/qstringconverter.cpp#L2085
| icu::UnicodeString unicodeString; | |
| UErrorCode errorCode; | |
| UConverter* latin9Converter = ucnv_open(targetCharset.toLocal8Bit().data(), &errorCode); | |
| if (!U_FAILURE(errorCode)) { | |
| ucnv_close(latin9Converter); | |
| return QJSValue::UndefinedValue; | |
| } | |
| char* result = nullptr; | |
| ucnv_fromUChars(latin9Converter, | |
| result, | |
| 0, | |
| &value.data()->unicode(), | |
| value.length(), | |
| &errorCode); | |
| ucnv_close(latin9Converter); | |
| QStringEncoder encoder = QStringEncoder(targetCharset); | |
| if (!encoder.isValid()) { | |
| return QJSValue::UndefinedValue; | |
| } | |
| QByteArray result = encoder.encode(value); |
There was a problem hiding this comment.
Our QT vcpkg is build with "icu"
https://github.com/daschuer/vcpkg/blob/5d58718e04ee9196314dc77d7cf414f36be24b65/ports/qtbase/vcpkg.json#L62
There was a problem hiding this comment.
@daschuer thank you for pointing this up. If I can't use QTextCodec, there's definitely some code I can copy here.
There was a problem hiding this comment.
So if I'm not mistaken, this should look like on my last commit. This, however doesn't work as expected. From JS side, convertEncoding returns an object that, printed using console.log, display the original string. Not a TypedArray. I can't figure out what's happening. I'm leaving it ofr tonight.
There was a problem hiding this comment.
thats weird. It should work. https://doc.qt.io/qt-6/qtqml-cppintegration-data.html#qbytearray-to-javascript-arraybuffer
There was a problem hiding this comment.
Maybe do some type introspection (instanceof) to confirm if it is an ArrayBuffer.
There was a problem hiding this comment.
Ah you're right. I expected a TypedArray and thus a .forEach(). So I'd need to do new Uint8Array(midi.convertEncoding("ISO-8859-15", "Thing to display")) from JS? Is there a way to directly return a TypedArray?
There was a problem hiding this comment.
You can technically execute some JS code in C++ that essentially does that and return the resulting QJSValue, but thats quite ugly IMO. I think it should up to the caller how they want to interpret that buffer. If thats too much typing for you, you can always create a utility function in your mapping, but lets not bake that into the API. It will cause API inconsistencies and unnecessary overhead.
899d84d to
b730583
Compare
| } | ||
|
|
||
| // Available charsets should be available here: | ||
| // http://www.iana.org/assignments/character-sets/character-sets.xhtml |
There was a problem hiding this comment.
Since the supported charsets depend on the Qt build options and the ICU version, we should only allow charsets from a positive list.
The charsets on this positive list can than be probed in the CMake configuration step, to ensure that nobody builds a Mixxx version that don't support this functionality of the mapping API:
# Add a custom command to compile the code
add_custom_command(
OUTPUT list_codecs
COMMAND ${CMAKE_CXX_COMPILER} -o list_codecs -xc++ - <<EOF
#include <QStringConverter>
#include <QStringList>
#include <QTextStream>
int main(int argc, char* argv[]) {
if (argc != 2) {
return 1; // Invalid number of arguments
}
QString charset = argv[1];
QStringList codecs = QStringConverter::availableCodecs();
if (codecs.contains(charset)) {
return 0; // Charset is available
} else {
return 1; // Charset is not available
}
}
EOF
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMENT "Compiling list_codecs"
)
# List of specified charsets to check
set(SPECIFIED_CHARSETS "UTF-8;ISO-8859-1;ISO-8859-15")
# Check for each specified charset
foreach(CHARSET IN LISTS SPECIFIED_CHARSETS)
execute_process(
COMMAND ${CMAKE_BINARY_DIR}/list_codecs ${CHARSET}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
RESULT_VARIABLE RETURN_CODE
)
if(${RETURN_CODE} EQUAL 0)
message(STATUS "Charset ${CHARSET} is available.")
else()
message(FATAL_ERROR "Charset ${CHARSET} is not available.")
endif()
endforeach()There was a problem hiding this comment.
alternatively we just let the mapping handle it. Not sure whats better.
There was a problem hiding this comment.
Meh. I'm not in favor of that. The idea is to allow controller developers to use charsets if available without having to open a PR to make it available. I can, however check against QStringConverter::availableCodecs() and QTextCodec::availableCodecs().
There was a problem hiding this comment.
Every Mixxx installation must support each mapping. The mapping API must be stable accross installations. There is no problem, to add a long list of charsets to the positive list.
There was a problem hiding this comment.
not sure if packagers are very happy about this though. Letting the mapping handle it (disable the corresponding feature) may make more sense. It entirely depends on the list.
There was a problem hiding this comment.
How likely is the case that QT is build without ICU? It is a core feature of every Linux distribution and for Window an macOs we provide our own solution.
In the unlikely case a certain codec is not available we get an empty string. This is much better than a totally failed mapping.
So I guess we can be relaxed here and use an easy solution like a simple isEmpt() sanity check or such.
There was a problem hiding this comment.
In the unlikely case a certain codec is not available we get an empty string
No. Current implementation returns undefined and I documented that it will return undefined if charset is unavailable. This lets mapping developers implement an alternative behavior.
|
Should I expose |
No, the API must guarantee to support the same charsets on all platforms and builds of Mixxx. |
That'll limit us to a somewhat small common subset though. Is that a price we're willing to pay? Do we want to tell a contributor "no we can not accept your mapping because one niche feature doesn't work on one particular platform"? |
I don't think so, if you use a Qt build with the same ICU version, you will get always the same charsets. We just need to ensure, that the configuration of the Mixxx build fails, if someone uses an inproper Qt build. |
|
Plus, it's not as if it poses the risk of a crash. At worst, the function would return |
b730583 to
531a483
Compare
531a483 to
659e759
Compare
d416c2c to
782993c
Compare
fb9ca37 to
74c5645
Compare
|
Looks good for a merge to me. |
12cc728 to
7936957
Compare
Swiftb0y
left a comment
There was a problem hiding this comment.
addressing @JoergAtGithub now since this is his code.
|
@Swiftb0y @JoergAtGithub I'm really sorry, the ongoing discussion is way above my skills. So just tell me what to do when you agree on a correct implementation. |
|
No worries. I'll create a PR to your branch. I think we settled on an implementation. |
@Swiftb0y Wait, I'm already on it. That's why the questions came up. |
|
Awesome. I'll be glad if you could explain me the changes when you're done. I like to learn. |
Allow to convert from UTF-8 to whatever encoding the device supports
controllerscriptinterfacelegacy.h by all encodings that work with all supported Qt versions. I explicitly did not added three charsets, which triggered an infinite loop of the QJSEngine thread for me. Made convertCharset function accepting string targetCharset private for safety reasons. Updated and added tests to ensure that we detect charsets that crash or result in unexpected output length. Added a deep copy of the targetcharset name reference Made encoder in Qt 6.4 case a self-deleting smart-pointer Optimize QAnyStringView handling for Qt >=6.8.0 Improved error output Unified behavior of the UCS2 charsets between platforms by adding Byte Order Mark if missing
0e55fb7 to
513c50f
Compare
|
|
||
| // ControlObjectScript connections are processed via QueuedConnection. Use | ||
| // processEvents() to cause Qt to deliver them. | ||
| processEvents(); | ||
| EXPECT_DOUBLE_EQ(counter->get(), charsetEnumEntry.keyCount()); | ||
| } |
There was a problem hiding this comment.
this would be more robust if instead of relying on an out-of-band CO, we would simply evaluate the code and use the expression result. At least this current approach seems very convoluted to me.
|
Dammit, this was supposed to be a simple feature. @JoergAtGithub feel free to take this to your own repo. |
|
Closing in favor of #13935 |
…2-charset-encoding-salvage Salvage #13772

Allow to convert from UTF-8 to whatever encoding the device supports