diff --git a/src/core/control/XournalMain.cpp b/src/core/control/XournalMain.cpp index 553704e5e218..a59e696194b3 100644 --- a/src/core/control/XournalMain.cpp +++ b/src/core/control/XournalMain.cpp @@ -85,7 +85,13 @@ void initCAndCoutLocales() { void initLocalisation() { #ifdef ENABLE_NLS fs::path localeDir = Util::getGettextFilepath(Util::getLocalePath()); + +#ifdef _WIN32 + wbindtextdomain(GETTEXT_PACKAGE, localeDir.wstring().c_str()); +#else bindtextdomain(GETTEXT_PACKAGE, localeDir.u8string().c_str()); +#endif + textdomain(GETTEXT_PACKAGE); #ifdef _WIN32 diff --git a/src/core/control/xojfile/LoadHandler.cpp b/src/core/control/xojfile/LoadHandler.cpp index c58bb23ff8d4..24d658f2c101 100644 --- a/src/core/control/xojfile/LoadHandler.cpp +++ b/src/core/control/xojfile/LoadHandler.cpp @@ -46,9 +46,9 @@ using std::string; } namespace { - constexpr size_t MAX_VERSION_LENGTH = 50; - constexpr size_t MAX_MIMETYPE_LENGTH = 25; -} +constexpr size_t MAX_VERSION_LENGTH = 50; +constexpr size_t MAX_MIMETYPE_LENGTH = 25; +} // namespace LoadHandler::LoadHandler(): attachedPdfMissing(false), @@ -316,7 +316,7 @@ void LoadHandler::parseContents() { double width = LoadHandlerHelper::getAttribDouble("width", this); double height = LoadHandlerHelper::getAttribDouble("height", this); - this->page = std::make_unique(width, height, /*suppressLayer*/true); + this->page = std::make_unique(width, height, /*suppressLayer*/ true); pages.push_back(this->page); } else if (strcmp(elementName, "audio") == 0) { @@ -373,7 +373,7 @@ void LoadHandler::parseBgSolid() { void LoadHandler::parseBgPixmap() { const char* domain = LoadHandlerHelper::getAttrib("domain", false, this); - const fs::path filepath(LoadHandlerHelper::getAttrib("filename", false, this)); + const fs::path filepath = fs::u8path(LoadHandlerHelper::getAttrib("filename", false, this)); // in case of a cloned background image, filename is a string representation of the page number from which the image // is cloned @@ -392,7 +392,8 @@ void LoadHandler::parseBgPixmap() { img.loadFile(fileToLoad, &error); if (error) { - error("%s", FC(_F("Could not read image: {1}. Error message: {2}") % fileToLoad.string() % error->message)); + error("%s", + FC(_F("Could not read image: {1}. Error message: {2}") % fileToLoad.u8string() % error->message)); g_error_free(error); } @@ -427,8 +428,8 @@ void LoadHandler::parseBgPixmap() { this->page->setBackgroundImage(img); } else if (!strcmp(domain, "clone")) { gchar* endptr = nullptr; - auto const& filename = filepath.string(); - size_t nr = static_cast(g_ascii_strtoull(filename.c_str(), &endptr, 10)); + auto const& filename = filepath.u8string(); + auto nr = static_cast(g_ascii_strtoull(filename.c_str(), &endptr, 10)); if (endptr == filename.c_str()) { error("%s", FC(_F("Could not read page number for cloned background image: {1}.") % filepath.string())); } @@ -583,11 +584,11 @@ void LoadHandler::parseStroke() { const char* fn = LoadHandlerHelper::getAttrib("fn", true, this); if (fn != nullptr && strlen(fn) > 0) { if (this->isGzFile) { - stroke->setAudioFilename(fn); + stroke->setAudioFilename(fs::u8path(fn)); } else { auto tempFile = getTempFileForPath(fn); if (!tempFile.empty()) { - stroke->setAudioFilename(tempFile.string()); + stroke->setAudioFilename(tempFile); } } } @@ -676,11 +677,11 @@ void LoadHandler::parseText() { const char* fn = LoadHandlerHelper::getAttrib("fn", true, this); if (fn != nullptr && strlen(fn) > 0) { if (this->isGzFile) { - text->setAudioFilename(fn); + text->setAudioFilename(fs::u8path(fn)); } else { auto tempFile = getTempFileForPath(fn); if (!tempFile.empty()) { - text->setAudioFilename(tempFile.string()); + text->setAudioFilename(tempFile); } } } @@ -1045,7 +1046,7 @@ void LoadHandler::parserText(GMarkupParseContext* context, const gchar* text, gs handler->stroke->setPressure(handler->pressureBuffer); } } else { - g_warning("%s", FC(_F("xoj-File: {1}") % handler->filepath.string().c_str())); + g_warning("%s", FC(_F("xoj-File: {1}") % handler->filepath.u8string())); g_warning("%s", FC(_F("Wrong number of pressure values, got {1}, expected {2}") % handler->pressureBuffer.size() % (handler->stroke->getPointCount() - 1))); } @@ -1161,7 +1162,7 @@ auto LoadHandler::readZipAttachment(fs::path const& filename) -> std::optional fs::path { return string(static_cast(tmpFilename)); } - error("%s", FC(_F("Requested temporary file was not found for attachment {1}") % filename.string())); + error("%s", FC(_F("Requested temporary file was not found for attachment {1}") % filename.u8string())); return ""; } diff --git a/src/core/control/xojfile/SaveHandler.cpp b/src/core/control/xojfile/SaveHandler.cpp index 6c8d15bd11d3..78553e346be9 100644 --- a/src/core/control/xojfile/SaveHandler.cpp +++ b/src/core/control/xojfile/SaveHandler.cpp @@ -254,7 +254,7 @@ void SaveHandler::visitPage(XmlNode* root, PageRef p, Document* doc, int id) { } } else { background->setAttrib("domain", "absolute"); - background->setAttrib("filename", doc->getPdfFilepath().string()); + background->setAttrib("filename", doc->getPdfFilepath().u8string()); } } background->setAttrib("pageno", p->getPdfPageNr() + 1); @@ -279,7 +279,7 @@ void SaveHandler::visitPage(XmlNode* root, PageRef p, Document* doc, int id) { p->getBackgroundImage().setCloneId(id); } else { background->setAttrib("domain", "absolute"); - background->setAttrib("filename", p->getBackgroundImage().getFilepath().string()); + background->setAttrib("filename", p->getBackgroundImage().getFilepath().u8string()); p->getBackgroundImage().setCloneId(id); } } else { diff --git a/src/core/model/Document.cpp b/src/core/model/Document.cpp index ea3c484788d0..765aa08a8666 100644 --- a/src/core/model/Document.cpp +++ b/src/core/model/Document.cpp @@ -1,6 +1,7 @@ #include "Document.h" #include +#include // for codecvt_utf8_utf16 #include // for size_t, localtime, strf... #include #include @@ -133,8 +134,9 @@ auto Document::createSaveFolder(fs::path lastSavePath) -> fs::path { return lastSavePath; } -auto Document::createSaveFilename(DocumentType type, const std::string& defaultSaveName, const std::string& defaultPdfName) -> fs::path { - constexpr static std::string_view forbiddenChars = {"\\/:*?\"<>|"}; +auto Document::createSaveFilename(DocumentType type, const std::string& defaultSaveName, + const std::string& defaultPdfName) -> fs::path { + constexpr static std::wstring_view forbiddenChars = {L"\\/:*?\"<>|"}; std::string wildcardString; if (type != Document::PDF) { if (!filepath.empty()) { @@ -154,13 +156,16 @@ auto Document::createSaveFilename(DocumentType type, const std::string& defaultS wildcardString = SaveNameUtils::parseFilenameFromWildcardString(defaultPdfName, this->filepath.filename()); } - const char* format = wildcardString.empty() ? defaultSaveName.c_str() : wildcardString.c_str(); + std::wstring_convert> converter; + + auto format_str = wildcardString.empty() ? defaultSaveName : wildcardString; + auto format = converter.from_bytes(format_str); // Todo (cpp20): use - std::ostringstream ss; + std::wostringstream ss; ss.imbue(std::locale()); time_t curtime = time(nullptr); - ss << std::put_time(localtime(&curtime), format); + ss << std::put_time(localtime(&curtime), format.c_str()); auto filename = ss.str(); // Todo (cpp20): use for (auto& c: filename) { diff --git "a/test/files/cjk/\346\265\213\350\257\225.pdf" "b/test/files/cjk/\346\265\213\350\257\225.pdf" new file mode 100644 index 000000000000..024d1d34b7b0 Binary files /dev/null and "b/test/files/cjk/\346\265\213\350\257\225.pdf" differ diff --git "a/test/files/cjk/\346\265\213\350\257\225.unzipped.xopp" "b/test/files/cjk/\346\265\213\350\257\225.unzipped.xopp" new file mode 100644 index 000000000000..2331cfa8e64e --- /dev/null +++ "b/test/files/cjk/\346\265\213\350\257\225.unzipped.xopp" @@ -0,0 +1,17 @@ + + +Xournal++ document - see https://xournalpp.github.io/ +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 + + + +Test +测试 +テスト + + + + + + + diff --git "a/test/files/cjk/\346\265\213\350\257\225.xopp" "b/test/files/cjk/\346\265\213\350\257\225.xopp" new file mode 100644 index 000000000000..b78f93059118 Binary files /dev/null and "b/test/files/cjk/\346\265\213\350\257\225.xopp" differ diff --git a/test/unit_tests/control/LoadHandlerTest.cpp b/test/unit_tests/control/LoadHandlerTest.cpp index 8f6e514a1be1..cb86c37fcd22 100644 --- a/test/unit_tests/control/LoadHandlerTest.cpp +++ b/test/unit_tests/control/LoadHandlerTest.cpp @@ -509,3 +509,32 @@ TEST(ControlLoadHandler, testStrokeWidthRecovery) { testPressureValues(8, {0.25, 0.30, 0.40, Point::NO_PRESSURE}); } + +TEST(ControlLoadHandler, testLoadStoreCJK) { + LoadHandler handler; + auto filepath = string(GET_TESTFILE("cjk/测试.xopp")); + Document* doc = handler.loadDocument(fs::u8path(filepath)); + ASSERT_NE(doc, nullptr); + + EXPECT_STREQ(doc->getPdfFilepath().filename().u8string().c_str(), u8"测试.pdf"); + + EXPECT_EQ((size_t)2, doc->getPageCount()); + const auto page = doc->getPage(0); + + EXPECT_EQ((size_t)1, page->getLayerCount()); + const auto* layer = (*page->getLayers())[0]; + + const auto& elements = layer->getElements(); + ASSERT_EQ((size_t)3, layer->getElements().size()); + + auto check_element = [&](int i, const char* answer) { + EXPECT_EQ(ELEMENT_TEXT, elements[i]->getType()); + auto* text = dynamic_cast(elements[i]); + ASSERT_NE(text, nullptr); + EXPECT_STREQ(text->getText().c_str(), answer); + }; + + check_element(0, u8"Test"); + check_element(1, u8"测试"); + check_element(2, u8"テスト"); +}