Skip to content

Commit

Permalink
Fix load/cast of QString to avoid issues with surrogate pairs.
Browse files Browse the repository at this point in the history
  • Loading branch information
Holt59 committed Dec 2, 2023
1 parent f4ee4da commit aac872c
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 20 deletions.
46 changes: 26 additions & 20 deletions src/pybind11-qt/pybind11_qt_basic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,36 @@ namespace pybind11::detail {
*/
bool type_caster<QString>::load(handle src, bool)
{

PyObject* objPtr = src.ptr();

if (!PyBytes_Check(objPtr) && !PyUnicode_Check(objPtr)) {
return false;
if (PyBytes_Check(objPtr)) {
value = QString::fromUtf8(PyBytes_AsString(objPtr));
return true;
}
else if (PyUnicode_Check(objPtr)) {
switch (PyUnicode_KIND(objPtr)) {
case PyUnicode_1BYTE_KIND:
value = QString::fromUtf8(PyUnicode_AsUTF8(objPtr));
break;
case PyUnicode_2BYTE_KIND:
value = QString::fromUtf16(
reinterpret_cast<char16_t*>(PyUnicode_2BYTE_DATA(objPtr)),
PyUnicode_GET_LENGTH(objPtr));
break;
case PyUnicode_4BYTE_KIND:
value = QString::fromUcs4(
reinterpret_cast<char32_t*>(PyUnicode_4BYTE_DATA(objPtr)),
PyUnicode_GET_LENGTH(objPtr));
break;
default:
return false;
}

// Ensure the string uses 8-bit characters
PyObject* strPtr =
PyUnicode_Check(objPtr) ? PyUnicode_AsUTF8String(objPtr) : objPtr;

if (!strPtr) {
return false;
return true;
}

// Extract the character data from the python string
value = QString::fromUtf8(PyBytes_AsString(strPtr));

// Deallocate local copy if one was made
if (strPtr != objPtr) {
Py_DecRef(strPtr);
else {
return false;
}

return true;
}

/**
Expand All @@ -71,8 +77,8 @@ namespace pybind11::detail {
handle type_caster<QString>::cast(QString src, return_value_policy /* policy */,
handle /* parent */)
{
static_assert(sizeof(QChar) == 2);
return PyUnicode_FromString(src.toStdString().c_str());
return PyUnicode_DecodeUTF16(reinterpret_cast<const char*>(src.utf16()),
2 * src.length(), nullptr, 0);
}

bool type_caster<QVariant>::load(handle src, bool)
Expand Down
8 changes: 8 additions & 0 deletions tests/python/test_qt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ PYBIND11_MODULE(qt, m)
{
// QString

m.def("create_qstring_with_emoji", []() {
return QString::fromUtf16(u"\U0001F600");
});

m.def("consume_qstring_with_emoji", [](QString const& qstring) {
return qstring.length();
});

m.def("qstring_to_stdstring", [](QString const& qstring) {
return qstring.toStdString();
});
Expand Down
9 changes: 9 additions & 0 deletions tests/python/test_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,19 @@ def test_qstring():

assert m.qstring_to_stdstring("éàüö") == "éàüö"
assert m.stdstring_to_qstring("éàüö") == "éàüö"
assert m.qstring_to_stdstring("خالد") == "خالد"
assert m.qstring_to_stdstring("🌎") == "🌎"

assert m.qstring_to_int("2") == 2
assert m.int_to_qstring(2) == "2"

emoji = m.create_qstring_with_emoji()

assert emoji.encode("utf-16be", "surrogatepass") == b"\xd8\x3d\xde\x00"
assert m.consume_qstring_with_emoji(emoji) == 2

assert m.consume_qstring_with_emoji("🌎") == 2


def test_qstringlist():
assert m.qstringlist_join([""], "--") == ""
Expand Down

0 comments on commit aac872c

Please sign in to comment.