Write and read projects in the C locale, and keep the seed whole
gtkmm sets both the C and the C++ global locale from the environment, so an unguarded stream writes "0,35" on a German desktop and groups 1920 as "1.920" — neither of which the reader, or any other JSON reader, can take back. Both the writer and the number parser now work in the classic locale: the writer imbues its stream, and the parser uses from_chars rather than stod, JSON having a point for a decimal mark whatever the user's mark is. The seed is read from the digits it was written as rather than from the double they parsed into: a 64-bit seed has more bits than a double has mantissa, and a seed that comes back off by a few is a project that no longer renders its own picture. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,11 +2,13 @@
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <clocale>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <locale>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -671,6 +673,51 @@ int main() {
|
||||
}
|
||||
check(threw, "a missing project file throws");
|
||||
|
||||
// A project is JSON whatever the user's locale is. gtkmm sets both the
|
||||
// C and the C++ global locale from the environment, so on a German
|
||||
// desktop an unguarded stream writes "0,35" and a grouped "1.920" —
|
||||
// which is not JSON, and the project could not be opened again.
|
||||
const char* const commaLocales[] = {"de_DE.UTF-8", "de_DE.utf8", "fr_FR.UTF-8",
|
||||
"fr_FR.utf8"};
|
||||
bool decimalCommaTried = false;
|
||||
for (const char* name : commaLocales) {
|
||||
if (std::setlocale(LC_ALL, name) == nullptr) continue;
|
||||
std::locale previous;
|
||||
try {
|
||||
previous = std::locale::global(std::locale(name));
|
||||
} catch (const std::exception&) {
|
||||
continue;
|
||||
}
|
||||
decimalCommaTried = true;
|
||||
|
||||
Project wide;
|
||||
wide.width = 1920;
|
||||
wide.height = 1080;
|
||||
wide.seed = 17353667313003709502ULL; // more bits than a double holds
|
||||
wide.stages.push_back({effectByName("grain"), {{"grain_amount", 0.35}}});
|
||||
|
||||
const auto text = projectToJson(wide);
|
||||
bool reread = false;
|
||||
try {
|
||||
const auto back = projectFromJson(text);
|
||||
reread = back.width == 1920 && back.height == 1080 &&
|
||||
back.seed == wide.seed &&
|
||||
paramDouble(back.stages[0].values, "grain_amount", -1.0) == 0.35;
|
||||
} catch (const EffectError&) {
|
||||
}
|
||||
check(text.find("0.35") != std::string::npos &&
|
||||
text.find("\"width\": 1920") != std::string::npos,
|
||||
std::string("a project is written as JSON under ") + name);
|
||||
check(reread, std::string("a project written under ") + name + " opens again");
|
||||
|
||||
std::locale::global(previous);
|
||||
std::setlocale(LC_ALL, "C");
|
||||
break;
|
||||
}
|
||||
if (!decimalCommaTried) {
|
||||
std::cout << " skip a decimal-comma locale (none installed)\n";
|
||||
}
|
||||
|
||||
std::filesystem::remove_all(dir);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user