Skip to content

Commit 2d94a3c

Browse files
committed
Fix usage of utf-8 filenames for windows
the std::filesystem::path mechanism is reasonable, but needs to be told it to look for a utf-8 string, and is not automatically that, and the standards committee must have realized there were issues and change the api in c++20 Signed-off-by: Kimball Thurston <kdt3rd@gmail.com>
1 parent 3144463 commit 2d94a3c

File tree

10 files changed

+208
-2
lines changed

10 files changed

+208
-2
lines changed

src/lib/OpenEXR/ImfStdIO.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
#include <ImfStdIO.h>
1616
#include <errno.h>
1717
#include <filesystem>
18+
#if __cplusplus >= 202002L
19+
# include <ranges>
20+
# include <span>
21+
#endif
1822

1923
using namespace std;
2024
#include "ImfNamespace.h"
@@ -27,15 +31,29 @@ namespace
2731
inline ifstream*
2832
make_ifstream (const char* filename)
2933
{
30-
return new ifstream (std::filesystem::path (filename),
34+
#if __cplusplus >= 202002L
35+
auto u8view = ranges::views::transform (span{filename, strlen(filename)},
36+
[](char c) -> char8_t { return c; });
37+
return new ifstream (filesystem::path (u8view.begin (), u8view.end ()),
3138
ios_base::in | ios_base::binary);
39+
#else
40+
return new ifstream (filesystem::u8path (filename),
41+
ios_base::in | ios_base::binary);
42+
#endif
3243
}
3344

3445
inline ofstream*
3546
make_ofstream (const char* filename)
3647
{
37-
return new ofstream (std::filesystem::path (filename),
48+
#if __cplusplus >= 202002L
49+
auto u8view = ranges::views::transform (span{filename, strlen(filename)},
50+
[](char c) -> char8_t { return c; });
51+
return new ofstream (filesystem::path (u8view.begin (), u8view.end ()),
52+
ios_base::out | ios_base::binary);
53+
#else
54+
return new ofstream (filesystem::u8path (filename),
3855
ios_base::out | ios_base::binary);
56+
#endif
3957
}
4058

4159
void

src/lib/OpenEXRCore/openexr_context.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,13 @@ typedef struct _exr_context_initializer_v3
361361
/** @brief Check the magic number of the file and report
362362
* `EXR_ERR_SUCCESS` if the file appears to be a valid file (or at least
363363
* has the correct magic number and can be read).
364+
*
365+
* The filename is assumed to be a UTF-8 encoded
366+
* filename, as is standard on current Unix filesystems. Under
367+
* windows, this will be widened to be a wchar_t. If the user provides
368+
* custom I/O routines, the responsibility passes to the user for that
369+
* behavior.
370+
*
364371
*/
365372
EXR_EXPORT exr_result_t exr_test_file_header (
366373
const char* filename, const exr_context_initializer_t* ctxtdata);
@@ -380,6 +387,12 @@ EXR_EXPORT exr_result_t exr_finish (exr_context_t* ctxt);
380387
* previously opened a stream, file, or whatever and placed relevant
381388
* data in userdata to access that.
382389
*
390+
* In the default case, the filename is assumed to be a UTF-8 encoded
391+
* filename, as is standard on current Unix filesystems. Under
392+
* windows, this will be widened to be a wchar_t. If the user provides
393+
* custom I/O routines, the responsibility passes to the user for that
394+
* behavior.
395+
*
383396
* One notable attribute of the context is that once it has been
384397
* created and returned a successful code, it has parsed all the
385398
* header data. This is done as one step such that it is easier to
@@ -415,6 +428,12 @@ typedef enum exr_default_write_mode
415428
* a stream, file, or whatever and placed relevant data in userdata to
416429
* access that.
417430
*
431+
* In the default case, the filename is assumed to be a UTF-8 encoded
432+
* filename, as is standard on current Unix filesystems. Under
433+
* windows, this will be widened to be a wchar_t. If the user provides
434+
* custom I/O routines, the responsibility passes to the user for that
435+
* behavior.
436+
*
418437
* Multi-Threading: To avoid issues with creating multi-part EXR
419438
* files, the library approaches writing as a multi-step process, so
420439
* the same concurrent guarantees can not be made for writing a

src/test/OpenEXRCoreTest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ define_openexrcore_tests(
9898
testStartWriteDeepScan
9999
testStartWriteTile
100100
testStartWriteDeepTile
101+
testStartWriteUTF8
101102
testWriteAttrs
102103
testWriteScans
103104
testWriteTiles

src/test/OpenEXRCoreTest/main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ main (int argc, char* argv[])
187187
TEST (testStartWriteTile, "core_write");
188188
TEST (testStartWriteDeepScan, "core_write");
189189
TEST (testStartWriteDeepTile, "core_write");
190+
TEST (testStartWriteUTF8, "core_write");
190191
TEST (testWriteScans, "core_write");
191192
TEST (testWriteTiles, "core_write");
192193
TEST (testWriteMultiPart, "core_write");

src/test/OpenEXRCoreTest/write.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
// SPDX-License-Identifier: BSD-3-Clause
22
// Copyright Contributors to the OpenEXR Project.
33

4+
#ifdef _WIN32
5+
// windows is very particular about when windows.h is included
6+
#include <windows.h>
7+
#include <fileapi.h>
8+
#include <inttypes.h>
9+
#include <strsafe.h>
10+
#else
11+
#include <unistd.h>
12+
#endif
13+
414
#include "write.h"
515

616
#include "test_value.h"
@@ -10,6 +20,7 @@
1020
#include <float.h>
1121
#include <limits.h>
1222
#include <math.h>
23+
#include <stdio.h>
1324
#include <string.h>
1425

1526
#include <iomanip>
@@ -1395,3 +1406,80 @@ testWriteMultiPart (const std::string& tempdir)
13951406
EXRCORE_TEST_RVAL (exr_finish (&outf));
13961407
remove (outfn.c_str ());
13971408
}
1409+
1410+
void
1411+
testStartWriteUTF8 (const std::string& tempdir)
1412+
{
1413+
exr_context_t outf;
1414+
// per google translate, image in Japanese
1415+
std::string outfn = tempdir + "画像.exr";
1416+
int partidx;
1417+
1418+
exr_context_initializer_t cinit = EXR_DEFAULT_CONTEXT_INITIALIZER;
1419+
cinit.error_handler_fn = &err_cb;
1420+
cinit.zip_level = 3;
1421+
cinit.flags |= EXR_CONTEXT_FLAG_WRITE_LEGACY_HEADER;
1422+
1423+
exr_set_default_zip_compression_level (-1);
1424+
1425+
EXRCORE_TEST_RVAL (exr_start_write (
1426+
&outf, outfn.c_str (), EXR_WRITE_FILE_DIRECTLY, &cinit));
1427+
EXRCORE_TEST_RVAL (
1428+
exr_add_part (outf, "beauty", EXR_STORAGE_SCANLINE, &partidx));
1429+
EXRCORE_TEST (partidx == 0);
1430+
EXRCORE_TEST_RVAL (exr_get_count (outf, &partidx));
1431+
EXRCORE_TEST (partidx == 1);
1432+
partidx = 0;
1433+
1434+
int fw = 1;
1435+
int fh = 1;
1436+
exr_attr_box2i_t dataW = { {0, 0}, {0, 0} };
1437+
1438+
EXRCORE_TEST_RVAL (
1439+
exr_initialize_required_attr_simple (outf, partidx, fw, fh, EXR_COMPRESSION_NONE));
1440+
EXRCORE_TEST_RVAL (exr_set_data_window (outf, partidx, &dataW));
1441+
1442+
EXRCORE_TEST_RVAL (exr_add_channel (
1443+
outf, partidx, "h", EXR_PIXEL_HALF, EXR_PERCEPTUALLY_LOGARITHMIC, 1, 1));
1444+
EXRCORE_TEST_RVAL (exr_write_header (outf));
1445+
1446+
exr_chunk_info_t cinfo;
1447+
exr_encode_pipeline_t encoder;
1448+
1449+
EXRCORE_TEST_RVAL (exr_write_scanline_chunk_info (outf, 0, 0, &cinfo));
1450+
EXRCORE_TEST_RVAL (
1451+
exr_encoding_initialize (outf, 0, &cinfo, &encoder));
1452+
1453+
uint16_t hval[] = { 0x1234, 0 };
1454+
for (int c = 0; c < encoder.channel_count; ++c)
1455+
{
1456+
encoder.channels[c].encode_from_ptr = (const uint8_t *)hval;
1457+
encoder.channels[c].user_pixel_stride = 2;
1458+
encoder.channels[c].user_line_stride = 2;
1459+
}
1460+
EXRCORE_TEST_RVAL (
1461+
exr_encoding_choose_default_routines (outf, 0, &encoder));
1462+
EXRCORE_TEST_RVAL (exr_encoding_run (outf, 0, &encoder));
1463+
EXRCORE_TEST_RVAL (exr_encoding_destroy (outf, &encoder));
1464+
1465+
EXRCORE_TEST_RVAL (exr_finish (&outf));
1466+
#ifdef _WIN32
1467+
int wcSize = 0, fnlen = 0;
1468+
wchar_t* wcFn = NULL;
1469+
1470+
fnlen = (int) strlen (outfn.c_str ());
1471+
wcSize = MultiByteToWideChar (CP_UTF8, 0, outfn.c_str (), fnlen, NULL, 0);
1472+
wcFn = (wchar_t*) malloc (sizeof (wchar_t) * (wcSize + 1));
1473+
if (wcFn)
1474+
{
1475+
MultiByteToWideChar (CP_UTF8, 0, outfn.c_str (), fnlen, wcFn, wcSize);
1476+
wcFn[wcSize] = 0;
1477+
}
1478+
EXRCORE_TEST ( _waccess (wcFn, 0) != -1 );
1479+
_wremove (wcFn);
1480+
free (wcFn);
1481+
#else
1482+
EXRCORE_TEST ( access (outfn.c_str (), F_OK) != -1 );
1483+
remove (outfn.c_str ());
1484+
#endif
1485+
}

src/test/OpenEXRCoreTest/write.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ void testStartWriteScan (const std::string& tempdir);
1616
void testStartWriteTile (const std::string& tempdir);
1717
void testStartWriteDeepScan (const std::string& tempdir);
1818
void testStartWriteDeepTile (const std::string& tempdir);
19+
void testStartWriteUTF8 (const std::string& tempdir);
1920

2021
void testUpdateMeta (const std::string& tempdir);
2122

src/test/OpenEXRTest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ define_openexr_tests(
167167
testDeepTiledBasic
168168
testDwaLookups
169169
testExistingStreams
170+
testExistingStreamsUTF8
170171
testFutureProofing
171172
testHeader
172173
testHuf

src/test/OpenEXRTest/main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ main (int argc, char* argv[])
202202
TEST (testTiledLineOrder, "basic");
203203
TEST (testScanLineApi, "basic");
204204
TEST (testExistingStreams, "core");
205+
TEST (testExistingStreamsUTF8, "core");
205206
TEST (testStandardAttributes, "core");
206207
TEST (testOptimized, "basic");
207208
TEST (testOptimizedInterleavePatterns, "basic");

src/test/OpenEXRTest/testExistingStreams.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,3 +1074,78 @@ testExistingStreams (const std::string& tempDir)
10741074
assert (false);
10751075
}
10761076
}
1077+
1078+
void
1079+
testExistingStreamsUTF8 (const std::string& tempDir)
1080+
{
1081+
1082+
cout << "Testing reading and writing using existing streams" << endl;
1083+
1084+
const int W = 119;
1085+
const int H = 237;
1086+
Array2D<Rgba> p1 (H, W);
1087+
1088+
fillPixels1 (p1, W, H);
1089+
1090+
// per google translate, image in Japanese
1091+
std::string outfn = tempDir + "画像.exr";
1092+
1093+
{
1094+
cout << "writing";
1095+
#ifdef _WIN32
1096+
_wremove (WidenFilename (outfn.c_str ()).c_str ());
1097+
#else
1098+
remove (outfn.c_str ());
1099+
#endif
1100+
Header header (
1101+
W,
1102+
H,
1103+
1,
1104+
IMATH_NAMESPACE::V2f (0, 0),
1105+
1,
1106+
INCREASING_Y,
1107+
NO_COMPRESSION);
1108+
1109+
RgbaOutputFile out (
1110+
outfn.c_str (),
1111+
header,
1112+
WRITE_RGBA);
1113+
1114+
out.setFrameBuffer (&p1[0][0], 1, W);
1115+
out.writePixels (H);
1116+
}
1117+
1118+
{
1119+
cout << ", reading";
1120+
RgbaInputFile in (outfn.c_str ());
1121+
const Box2i& dw = in.dataWindow ();
1122+
int w = dw.max.x - dw.min.x + 1;
1123+
int h = dw.max.y - dw.min.y + 1;
1124+
int dx = dw.min.x;
1125+
int dy = dw.min.y;
1126+
1127+
Array2D<Rgba> p2 (h, w);
1128+
in.setFrameBuffer (&p2[-dy][-dx], 1, w);
1129+
in.readPixels (dw.min.y, dw.max.y);
1130+
1131+
cout << ", comparing";
1132+
for (int y = 0; y < h; ++y)
1133+
{
1134+
for (int x = 0; x < w; ++x)
1135+
{
1136+
assert (p2[y][x].r == p1[y][x].r);
1137+
assert (p2[y][x].g == p1[y][x].g);
1138+
assert (p2[y][x].b == p1[y][x].b);
1139+
assert (p2[y][x].a == p1[y][x].a);
1140+
}
1141+
}
1142+
}
1143+
1144+
cout << endl;
1145+
1146+
#ifdef _WIN32
1147+
_wremove (WidenFilename (outfn.c_str ()).c_str ());
1148+
#else
1149+
remove (outfn.c_str ());
1150+
#endif
1151+
}

src/test/OpenEXRTest/testExistingStreams.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
#include <string>
77

88
void testExistingStreams (const std::string& tempDir);
9+
void testExistingStreamsUTF8 (const std::string& tempDir);

0 commit comments

Comments
 (0)