From aa1f4cc9109f54c64227630b724f9c2c4404bcef Mon Sep 17 00:00:00 2001 From: Tom Schoonjans Date: Thu, 2 Nov 2017 16:55:22 +0000 Subject: [PATCH] Use UTF-8 filenames on Windows Until now fopen was used on Windows to open files for reading and writing. This assumed however that the filename would be encoded in the current codepage, which is a major inconvenience and makes it even impossible to use filenames that use characters from more than one codepage. This patch enforces the use of UTF-8 filenames on all platforms. Based on the work of Owen Taylor (https://lists.cairographics.org/archives/cairo/2007-February/009591.html) --- src/cairo-misc.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++ src/cairo-output-stream.c | 8 ++++++- src/cairo-png.c | 20 ++++++++++++----- src/cairoint.h | 4 ++++ 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/cairo-misc.c b/src/cairo-misc.c index 19629feca..acc681471 100644 --- a/src/cairo-misc.c +++ b/src/cairo-misc.c @@ -40,6 +40,8 @@ #include "cairoint.h" #include "cairo-error-private.h" +#include +#include COMPILE_TIME_ASSERT ((int)CAIRO_STATUS_LAST_STATUS < (int)CAIRO_INT_STATUS_UNSUPPORTED); COMPILE_TIME_ASSERT (CAIRO_INT_STATUS_LAST_STATUS <= 127); @@ -842,6 +844,61 @@ _cairo_strtod (const char *nptr, char **endptr) return value; } +/** + * _cairo_fopen: + * @filename: filename to open + * @mode: mode string with which to open the file + * @file_out: reference to file handle + * + * Exactly like the C library function, but possibly doing encoding + * conversion on the filename. On all platforms, the filename is + * passed directly to the system, but on Windows, the filename is + * interpreted as UTF-8, rather than in a codepage that would depend + * on system settings. + * + * Return value: CAIRO_STATUS_SUCCESS when the filename was converted successfully to the native encoding, or the error reported by _cairo_utf8_to_utf16 otherwise. To check if the file handle could be obtained, dereference file_out and compare its value against NULL + **/ +cairo_status_t +_cairo_fopen (const char *filename, const char *mode, FILE **file_out) +{ + FILE *result; +#ifdef _WIN32 /* also defined on x86_64 */ + uint16_t *filename_w; + uint16_t *mode_w; + cairo_status_t status; + + *file_out = NULL; + + if (filename == NULL || mode == NULL) { + errno = EINVAL; + return CAIRO_STATUS_SUCCESS; + } + + if ((status = _cairo_utf8_to_utf16 (filename, -1, &filename_w, NULL)) != CAIRO_STATUS_SUCCESS) { + errno = EINVAL; + return status; + } + + if ((status = _cairo_utf8_to_utf16 (mode, -1, &mode_w, NULL)) != CAIRO_STATUS_SUCCESS) { + free (filename_w); + errno = EINVAL; + return status; + } + + result = _wfopen(filename_w, mode_w); + + free (filename_w); + free (mode_w); + +#else /* Use fopen directly */ + result = fopen (filename, mode); +#endif + + *file_out = result; + + return CAIRO_STATUS_SUCCESS; +} + #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN diff --git a/src/cairo-output-stream.c b/src/cairo-output-stream.c index f43f212ed..1cb077ff3 100644 --- a/src/cairo-output-stream.c +++ b/src/cairo-output-stream.c @@ -657,11 +657,16 @@ _cairo_output_stream_create_for_filename (const char *filename) { stdio_stream_t *stream; FILE *file; + cairo_status_t status; if (filename == NULL) return _cairo_null_stream_create (); - file = fopen (filename, "wb"); + status = _cairo_fopen (filename, "wb", &file); + + if (status != CAIRO_STATUS_SUCCESS) + return _cairo_output_stream_create_in_error (status); + if (file == NULL) { switch (errno) { case ENOMEM: @@ -804,3 +809,4 @@ _cairo_null_stream_create (void) return stream; } + diff --git a/src/cairo-png.c b/src/cairo-png.c index eab2537bc..e52e56578 100644 --- a/src/cairo-png.c +++ b/src/cairo-png.c @@ -345,7 +345,8 @@ stdio_write_func (png_structp png, png_bytep data, png_size_t size) /** * cairo_surface_write_to_png: * @surface: a #cairo_surface_t with pixel contents - * @filename: the name of a file to write to + * @filename: the name of a file to write to; on Windows this filename + * is encoded in UTF-8. * * Writes the contents of @surface to a new file @filename as a PNG * image. @@ -373,7 +374,11 @@ cairo_surface_write_to_png (cairo_surface_t *surface, if (surface->finished) return _cairo_error (CAIRO_STATUS_SURFACE_FINISHED); - fp = fopen (filename, "wb"); + status = _cairo_fopen (filename, "wb", &fp); + + if (status != CAIRO_STATUS_SUCCESS) + return _cairo_error (status); + if (fp == NULL) { switch (errno) { case ENOMEM: @@ -738,7 +743,8 @@ read_png (struct png_read_closure_t *png_closure) /** * cairo_image_surface_create_from_png: - * @filename: name of PNG file to load + * @filename: name of PNG file to load. On Windows this filename + * is encoded in UTF-8. * * Creates a new image surface and initializes the contents to the * given PNG file. @@ -764,10 +770,14 @@ cairo_image_surface_create_from_png (const char *filename) { struct png_read_closure_t png_closure; cairo_surface_t *surface; + cairo_status_t status; + + status = _cairo_fopen (filename, "rb", (FILE **) &png_closure.closure); + + if (status != CAIRO_STATUS_SUCCESS) + return _cairo_surface_create_in_error (status); - png_closure.closure = fopen (filename, "rb"); if (png_closure.closure == NULL) { - cairo_status_t status; switch (errno) { case ENOMEM: status = _cairo_error (CAIRO_STATUS_NO_MEMORY); diff --git a/src/cairoint.h b/src/cairoint.h index 6f8303c3c..6cd45855a 100644 --- a/src/cairoint.h +++ b/src/cairoint.h @@ -1919,6 +1919,10 @@ _cairo_matrix_multiply (cairo_matrix_t *r, cairo_private void _cairo_observers_notify (cairo_list_t *observers, void *arg); +/* Open a file with a UTF-8 filename */ +cairo_private cairo_status_t +_cairo_fopen (const char *filename, const char *mode, FILE **file_out); + /* Avoid unnecessary PLT entries. */ slim_hidden_proto (cairo_clip_preserve); slim_hidden_proto (cairo_close_path); -- 2.14.2