For Windows, assume outfile & API filename args are in UTF-8,

& use xxxW() APIs accordingly, ticket #288, props Marcel
  **Backwards-incompatible change**
This commit is contained in:
gitlost 2023-05-10 00:47:44 +01:00
parent cc69c86129
commit 15fdca2a03
12 changed files with 151 additions and 47 deletions

View file

@ -10,6 +10,8 @@ Version 2.12.0.9 (dev) not released yet
- Text (HRT) placement for vector (EMF/EPS/SVG) output changed - for EAN/UPC
slightly further away from barcode, for all others slightly nearer. Some
horizontal alignments of EAN/UPC vector text also tweaked
- For Windows, filenames are now assumed to be UTF-8 encoded. Affects `outfile`
in `zint_symbol` and all API filename arguments
Changes
-------

View file

@ -38,6 +38,7 @@
#include "common.h"
#include "eci.h"
#include "gs1.h"
#include "output.h"
#include "zfiletypes.h"
/* It's assumed that int is at least 32 bits, the following will compile-time fail if not
@ -257,7 +258,11 @@ static int dump_plot(struct zint_symbol *symbol) {
if (output_to_stdout) {
f = stdout;
} else {
#ifdef _WIN32
f = out_win_fopen(symbol->outfile, "w");
#else
f = fopen(symbol->outfile, "w");
#endif
if (!f) {
strcpy(symbol->errtxt, "201: Could not open output file");
return ZINT_ERROR_FILE_ACCESS;
@ -1453,7 +1458,11 @@ int ZBarcode_Encode_File(struct zint_symbol *symbol, const char *filename) {
file = stdin;
fileLen = ZINT_MAX_DATA_LEN;
} else {
#ifdef _WIN32
file = out_win_fopen(filename, "rb");
#else
file = fopen(filename, "rb");
#endif
if (!file) {
sprintf(symbol->errtxt, "229: Unable to read input file (%d: %.30s)", errno, strerror(errno));
return error_tag(symbol, ZINT_ERROR_INVALID_DATA, NULL);

View file

@ -871,15 +871,40 @@ INTERNAL void out_upcean_split_text(const int upceanflag, const unsigned char te
}
}
#ifdef _WIN32
/* Convert UTF-8 to Windows wide chars. Ticket #288, props Marcel */
#define utf8_to_wide(u, w, r) \
{ \
int lenW; /* Includes NUL terminator */ \
if ((lenW = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, u, -1, NULL, 0)) == 0) return r; \
w = (wchar_t *) z_alloca(sizeof(wchar_t) * lenW); \
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, u, -1, w, lenW) == 0) return r; \
}
/* Do `fopen()` on Windows, assuming `filename` is UTF-8 encoded. Ticket #288, props Marcel */
INTERNAL FILE *out_win_fopen(const char *filename, const char *mode) {
wchar_t *filenameW, *modeW;
utf8_to_wide(filename, filenameW, NULL);
utf8_to_wide(mode, modeW, NULL);
return _wfopen(filenameW, modeW);
}
#endif
/* Make a directory; already existing dir okay */
/* Adapted from https://gist.github.com/JonathonReinhart/8c0d90191c38af2dcadb102c4e202950 and
https://nachtimwald.com/2019/07/10/recursive-create-directory-in-c-revisited/ */
static int out_maybe_mkdir(const char* path) {
static int out_maybe_mkdir(const char *path) {
#ifdef _WIN32
DWORD dwAttrib;
wchar_t *pathW;
/* Assumes `path` is UTF-8 encoded */
utf8_to_wide(path, pathW, 0);
/* Try to make the directory */
if (CreateDirectoryA(path, NULL) != 0) { /* Non-zero on success */
if (CreateDirectoryW(pathW, NULL) != 0) { /* Non-zero on success */
return 0;
}
/* If it fails for any reason but already exists, fail */
@ -887,7 +912,7 @@ static int out_maybe_mkdir(const char* path) {
return -1;
}
/* Check if the existing path is a directory */
if ((dwAttrib = GetFileAttributesA(path)) == (DWORD) -1 || !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)) {
if ((dwAttrib = GetFileAttributesW(pathW)) == (DWORD) -1 || !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)) {
return -1;
}
#else
@ -914,7 +939,11 @@ static int out_maybe_mkdir(const char* path) {
INTERNAL FILE *out_fopen(const char filename[256], const char *mode) {
FILE *outfile;
#ifdef _WIN32
if (!(outfile = out_win_fopen(filename, mode))) {
#else
if (!(outfile = fopen(filename, mode))) {
#endif
char dirname[256];
char *d;
#ifdef _WIN32
@ -950,7 +979,11 @@ INTERNAL FILE *out_fopen(const char filename[256], const char *mode) {
*d = '/'; /* Restore */
}
}
#ifdef _WIN32
outfile = out_win_fopen(filename, mode);
#else
outfile = fopen(filename, mode);
#endif
}
return outfile;

View file

@ -71,6 +71,11 @@ INTERNAL void out_upcean_split_text(const int upceanflag, const unsigned char te
/* Create output file, creating sub-directories if necessary. Returns `fopen()` FILE pointer */
INTERNAL FILE *out_fopen(const char filename[256], const char *mode);
#ifdef _WIN32
/* Do `fopen()` on Windows, assuming `filename` is UTF-8 encoded. Props Marcel, ticket #288 */
INTERNAL FILE *out_win_fopen(const char *filename, const char *mode);
#endif
#ifdef __cplusplus
}
#endif /* __cplusplus */

View file

@ -259,6 +259,8 @@ static void test_fopen(const testCtx *const p_ctx) {
/* 11*/ { "out_test//", "", "out.png", 1 },
/* 12*/ { "out_test/", "/out_test_subdir/", "out.png", 1 },
/* 13*/ { "out_test\\", "\\out_test_subdir\\", "out.png", 1 },
/* 14*/ { "", "", "outé.png", 1 },
/* 15*/ { "outé_test", "", "outé.png", 1 },
};
int data_size = ARRAY_SIZE(data);
int i, len;
@ -305,7 +307,7 @@ static void test_fopen(const testCtx *const p_ctx) {
if (data[i].dir[0]) {
assert_nonzero(testUtilDirExists(dirname), "i:%d testUtilDirExists(%s) != 0 (%d: %s)\n", i, dirname, errno, strerror(errno));
}
assert_zero(remove(outfile), "i:%d remove(%s) != 0 (%d: %s)\n", i, outfile, errno, strerror(errno));
assert_zero(testUtilRemove(outfile), "i:%d testUtilRemove(%s) != 0 (%d: %s)\n", i, outfile, errno, strerror(errno));
if (data[i].dir[0]) {
if (data[i].subdir[0] && !subdir_exists) {
assert_zero(testUtilRmDir(subdirname), "i:%d rmdir(%s) != 0 (%d: %s)\n", i, subdirname, errno, strerror(errno));

View file

@ -49,6 +49,7 @@
#include "testcommon.h"
#include "../eci.h"
#include "../output.h"
static int tests = 0;
static int failed = 0;
@ -107,6 +108,16 @@ void assert_notequal(int e1, int e2, const char *fmt, ...) {
}
#endif
#ifdef _WIN32
#define utf8_to_wide(u, w) \
{ \
int lenW; /* Includes NUL terminator */ \
if ((lenW = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, u, -1, NULL, 0)) == 0) return 0; \
w = (wchar_t *) z_alloca(sizeof(wchar_t) * lenW); \
if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, u, -1, w, lenW) == 0) return 0; \
}
#endif
/* Begin individual test function */
void testStartReal(const char *func, const char *name) {
tests++;
@ -1325,7 +1336,11 @@ int testUtilDataPath(char *buffer, int buffer_size, const char *subdir, const ch
/* Does file exist? */
int testUtilExists(const char *filename) {
#ifdef _WIN32
FILE *fp = out_win_fopen(filename, "r");
#else
FILE *fp = fopen(filename, "r");
#endif
if (fp == NULL) {
return 0;
}
@ -1333,10 +1348,24 @@ int testUtilExists(const char *filename) {
return 1;
}
/* Remove a file (Windows compatibility). Returns 0 if successful, non-zero if not */
int testUtilRemove(const char *filename) {
#ifdef _WIN32
wchar_t *filenameW;
utf8_to_wide(filename, filenameW);
return DeleteFileW(filenameW) == 0; /* Non-zero on success */
#else
return remove(filename);
#endif
}
/* Does directory exist? (Windows compatibility) */
int testUtilDirExists(const char *dirname) {
#ifdef _WIN32
DWORD dwAttrib = GetFileAttributes(dirname);
DWORD dwAttrib;
wchar_t *dirnameW;
utf8_to_wide(dirname, dirnameW);
dwAttrib = GetFileAttributesW(dirnameW);
return dwAttrib != (DWORD) -1 && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY);
#else
return testUtilExists(dirname);
@ -1346,7 +1375,9 @@ int testUtilDirExists(const char *dirname) {
/* Make a directory (Windows compatibility). Returns 0 if successful, non-zero if not */
int testUtilMkDir(const char *dirname) {
#ifdef _WIN32
return CreateDirectory(dirname, NULL) == 0;
wchar_t *dirnameW;
utf8_to_wide(dirname, dirnameW);
return CreateDirectoryW(dirnameW, NULL) == 0;
#else
return mkdir(dirname, S_IRWXU);
#endif
@ -1355,7 +1386,9 @@ int testUtilMkDir(const char *dirname) {
/* Remove a directory (Windows compatibility). Returns 0 if successful, non-zero if not */
int testUtilRmDir(const char *dirname) {
#ifdef _WIN32
return RemoveDirectory(dirname) == 0;
wchar_t *dirnameW;
utf8_to_wide(dirname, dirnameW);
return RemoveDirectoryW(dirnameW) == 0;
#else
return rmdir(dirname);
#endif
@ -1364,15 +1397,25 @@ int testUtilRmDir(const char *dirname) {
/* Rename a file (Windows compatibility) */
int testUtilRename(const char *oldpath, const char *newpath) {
#ifdef _MSVC
int ret = remove(newpath);
wchar_t *oldpathW, *newpathW;
int ret = testUtilRemove(newpath);
if (ret != 0) return ret;
#endif
utf8_to_wide(oldpath, oldpathW);
utf8_to_wide(newpath, newpathW);
return _wrename(oldpathW, newpathW);
#else
return rename(oldpath, newpath);
#endif
}
/* Create read-only file */
int testUtilCreateROFile(const char *filename) {
#ifdef _WIN32
wchar_t *filenameW;
FILE *fp = out_win_fopen(filename, "w+");
#else
FILE *fp = fopen(filename, "w+");
#endif
if (fp == NULL) {
return 0;
}
@ -1380,7 +1423,8 @@ int testUtilCreateROFile(const char *filename) {
return 0;
}
#ifdef _WIN32
if (SetFileAttributesA(filename, GetFileAttributesA(filename) | FILE_ATTRIBUTE_READONLY) == 0) {
utf8_to_wide(filename, filenameW);
if (SetFileAttributesW(filenameW, GetFileAttributesW(filenameW) | FILE_ATTRIBUTE_READONLY) == 0) {
return 0;
}
#else
@ -1394,11 +1438,13 @@ int testUtilCreateROFile(const char *filename) {
/* Remove read-only file (Windows compatibility) */
int testUtilRmROFile(const char *filename) {
#ifdef _WIN32
if (SetFileAttributesA(filename, GetFileAttributesA(filename) & ~FILE_ATTRIBUTE_READONLY) == 0) {
wchar_t *filenameW;
utf8_to_wide(filename, filenameW);
if (SetFileAttributesW(filenameW, GetFileAttributesW(filenameW) & ~FILE_ATTRIBUTE_READONLY) == 0) {
return -1;
}
#endif
return remove(filename);
return testUtilRemove(filename);
}
/* Compare 2 PNG files */

View file

@ -160,6 +160,7 @@ int testUtilBitmapCmp(const struct zint_symbol *symbol, const char *expected, in
int testUtilDataPath(char *buffer, int buffer_size, const char *subdir, const char *filename);
int testUtilExists(const char *filename);
int testUtilRemove(const char *filename);
int testUtilDirExists(const char *dirname);
int testUtilMkDir(const char *dirname);
int testUtilRmDir(const char *dirname);

View file

@ -1,6 +1,6 @@
% Zint Barcode Generator and Zint Barcode Studio User Manual
% Version 2.12.0.9
% February 2023
% May 2023
# 1. Introduction
@ -561,6 +561,8 @@ created if they don't already exist:
zint -o "dir/subdir/filename.eps" -d "This Text"
```
Note that on Windows, filenames are assumed to be UTF-8 encoded.
## 4.3 Selecting Barcode Type
Selecting which type of barcode you wish to produce (i.e. which symbology to
@ -1626,7 +1628,8 @@ values are 0, 90, 180 and 270.
The `ZBarcode_Encode_File()` and `ZBarcode_Encode_File_and_Print()` functions
can be used to encode data read directly from a text file where the filename is
given in the `NUL`-terminated `filename` string. The special filename `"-"`
(single hyphen) can be used to read from stdin.
(single hyphen) can be used to read from stdin. Note that on Windows, filenames
are assumed to be UTF-8 encoded.
If printing more than one barcode, the `zint_symbol` structure may be re-used by
calling the `ZBarcode_Clear()` function after each barcode to free any output
@ -1809,7 +1812,7 @@ Variable Name Type Meaning Default Value
`.emf`, `.eps`, `.pcx`,
`.svg`, `.tif` or `.txt`
followed by a terminating
`NUL`.
`NUL`.[^8]
`primary` character Primary message data for `""` (empty)
string more complex symbols, with
@ -1910,6 +1913,8 @@ QR Code (including HIBC, Micro QR, rMQR and UPNQR), and Ultracode - all of which
have a fixed width-to-height ratio (or, in the case of Code One, a fixed
height).
[^8]: For Windows, `outfile` is assumed to be UTF-8 encoded.
To alter these values use the syntax shown in the example below. This code has
the same result as the previous example except the output is now taller and
plotted in green.
@ -1939,9 +1944,8 @@ background alpha to `"00"` where the values for R, G and B will be ignored:
## 5.7 Handling Errors
If errors occur during encoding a non-zero integer value is passed back to the
calling application. In addition the `errtxt` variable is used to give a message
detailing the nature of the error. The errors generated by Zint are given in the
table below:
calling application. In addition the `errtxt` variable is set to a message
detailing the nature of the error. The errors generated by Zint are:
--------------------------------------------------------------------------------
Return Value Meaning
@ -2067,10 +2071,10 @@ Value Effect
------------------------- -----------------------------------------------------
0 No options selected.
`BARCODE_BIND_TOP` Boundary bar above the symbol only.[^8]
`BARCODE_BIND_TOP` Boundary bar above the symbol only.[^9]
`BARCODE_BIND` Boundary bars above and below the symbol and between
rows if stacking multiple symbols.[^9]
rows if stacking multiple symbols.[^10]
`BARCODE_BOX` Add a box surrounding the symbol and whitespace.
@ -2095,7 +2099,7 @@ Value Effect
separate colour channels (`OUT_BUFFER` only).
`BARCODE_QUIET_ZONES` Add compliant quiet zones (additional to any
specified whitespace).[^10]
specified whitespace).[^11]
`BARCODE_NO_QUIET_ZONES` Disable quiet zones, notably those with defaults.
@ -2105,13 +2109,13 @@ Value Effect
Table: API `output_options` Values {#tbl:api_output_options tag="$ $"}
[^8]: The `BARCODE_BIND_TOP` flag is set by default for DPD - see [6.1.10.7 DPD
[^9]: The `BARCODE_BIND_TOP` flag is set by default for DPD - see [6.1.10.7 DPD
Code].
[^9]: The `BARCODE_BIND` flag is always set for Codablock-F, Code 16K and Code
[^10]: The `BARCODE_BIND` flag is always set for Codablock-F, Code 16K and Code
49. Special considerations apply to ITF-14 - see [6.1.2.6 ITF-14].
[^10]: Codablock-F, Code 16K, Code 49, EAN-2 to EAN-13, ISBN, ITF-14, UPC-A and
[^11]: Codablock-F, Code 16K, Code 49, EAN-2 to EAN-13, ISBN, ITF-14, UPC-A and
UPC-E have compliant quiet zones added by default.
\clearpage
@ -2865,13 +2869,13 @@ Alphabet No. 1 (ISO/IEC 8859-1)].
![`zint -b CODE128AB -d "130170X178"`](images/code128ab.svg)
It is sometimes advantageous to stop Code 128 from using Code Set C which
compresses numerical data. The `BARCODE_CODE128AB`[^11] variant (symbology 60)
compresses numerical data. The `BARCODE_CODE128AB`[^12] variant (symbology 60)
suppresses Code Set C in favour of Code Sets A and B.
Note that the special escapes to manually switch Code Sets mentioned above are
not available for this variant (nor for any other).
[^11]: `BARCODE_CODE128AB` previously used the name `BARCODE_CODE128B`, which is
[^12]: `BARCODE_CODE128AB` previously used the name `BARCODE_CODE128B`, which is
still recognised.
#### 6.1.10.3 GS1-128

View file

@ -1,6 +1,6 @@
Zint Barcode Generator and Zint Barcode Studio User Manual
Version 2.12.0.9
February 2023
May 2023
*******************************************************************************
* For reference the following is a text-only version of the Zint manual, *
@ -701,6 +701,8 @@ created if they dont already exist:
zint -o "dir/subdir/filename.eps" -d "This Text"
Note that on Windows, filenames are assumed to be UTF-8 encoded.
4.3 Selecting Barcode Type
Selecting which type of barcode you wish to produce (i.e. which symbology to
@ -1668,7 +1670,8 @@ values are 0, 90, 180 and 270.
The ZBarcode_Encode_File() and ZBarcode_Encode_File_and_Print() functions can be
used to encode data read directly from a text file where the filename is given
in the NUL-terminated filename string. The special filename "-" (single hyphen)
can be used to read from stdin.
can be used to read from stdin. Note that on Windows, filenames are assumed to
be UTF-8 encoded.
If printing more than one barcode, the zint_symbol structure may be re-used by
calling the ZBarcode_Clear() function after each barcode to free any output
@ -1836,7 +1839,7 @@ encoding stages. The zint_symbol structure consists of the following variables:
in .png, .gif, .bmp, .emf,
.eps, .pcx, .svg, .tif or
.txt followed by a
terminating NUL.
terminating NUL.[8]
primary character Primary message data for "" (empty)
string more complex symbols, with a
@ -1956,9 +1959,8 @@ background alpha to "00" where the values for R, G and B will be ignored:
5.7 Handling Errors
If errors occur during encoding a non-zero integer value is passed back to the
calling application. In addition the errtxt variable is used to give a message
detailing the nature of the error. The errors generated by Zint are given in the
table below:
calling application. In addition the errtxt variable is set to a message
detailing the nature of the error. The errors generated by Zint are:
-------------------------------------------------------------------------------
Return Value Meaning
@ -2077,10 +2079,10 @@ together when adjusting this value:
-------------------------- ----------------------------------------------------
0 No options selected.
BARCODE_BIND_TOP Boundary bar above the symbol only.[8]
BARCODE_BIND_TOP Boundary bar above the symbol only.[9]
BARCODE_BIND Boundary bars above and below the symbol and between
rows if stacking multiple symbols.[9]
rows if stacking multiple symbols.[10]
BARCODE_BOX Add a box surrounding the symbol and whitespace.
@ -2105,7 +2107,7 @@ together when adjusting this value:
separate colour channels (OUT_BUFFER only).
BARCODE_QUIET_ZONES Add compliant quiet zones (additional to any
specified whitespace).[10]
specified whitespace).[11]
BARCODE_NO_QUIET_ZONES Disable quiet zones, notably those with defaults.
@ -2806,7 +2808,7 @@ Alphabet No. 1 (ISO/IEC 8859-1).
[zint -b CODE128AB -d "130170X178"]
It is sometimes advantageous to stop Code 128 from using Code Set C which
compresses numerical data. The BARCODE_CODE128AB[11] variant (symbology 60)
compresses numerical data. The BARCODE_CODE128AB[12] variant (symbology 60)
suppresses Code Set C in favour of Code Sets A and B.
Note that the special escapes to manually switch Code Sets mentioned above are
@ -4406,7 +4408,7 @@ defined.
Annex B. Man Page ZINT(1)
% ZINT(1) Version 2.12.0.9 % % February 2023
% ZINT(1) Version 2.12.0.9 % % May 2023
NAME
@ -4999,13 +5001,15 @@ Code (including HIBC, Micro QR, rMQR and UPNQR), and Ultracode - all of which
have a fixed width-to-height ratio (or, in the case of Code One, a fixed
height).
[8] The BARCODE_BIND_TOP flag is set by default for DPD - see 6.1.10.7 DPD Code.
[8] For Windows, outfile is assumed to be UTF-8 encoded.
[9] The BARCODE_BIND flag is always set for Codablock-F, Code 16K and Code 49.
[9] The BARCODE_BIND_TOP flag is set by default for DPD - see 6.1.10.7 DPD Code.
[10] The BARCODE_BIND flag is always set for Codablock-F, Code 16K and Code 49.
Special considerations apply to ITF-14 - see 6.1.2.6 ITF-14.
[10] Codablock-F, Code 16K, Code 49, EAN-2 to EAN-13, ISBN, ITF-14, UPC-A and
[11] Codablock-F, Code 16K, Code 49, EAN-2 to EAN-13, ISBN, ITF-14, UPC-A and
UPC-E have compliant quiet zones added by default.
[11] BARCODE_CODE128AB previously used the name BARCODE_CODE128B, which is still
[12] BARCODE_CODE128AB previously used the name BARCODE_CODE128B, which is still
recognised.

View file

@ -14,7 +14,7 @@
. ftr VB CB
. ftr VBI CBI
.\}
.TH "ZINT" "1" "February 2023" "Version 2.12.0.9" ""
.TH "ZINT" "1" "May 2023" "Version 2.12.0.9" ""
.hy
.SH NAME
.PP

View file

@ -1,6 +1,6 @@
% ZINT(1) Version 2.12.0.9
%
% February 2023
% May 2023
# NAME

View file

@ -1162,7 +1162,6 @@ int main(int argc, char **argv) {
float float_opt;
char errbuf[64]; /* For `validate_float()` */
arg_opt *arg_opts = (arg_opt *) z_alloca(sizeof(arg_opt) * argc);
int no_getopt_error = 1;
const int no_png = ZBarcode_NoPng();
@ -1183,7 +1182,7 @@ int main(int argc, char **argv) {
#endif
opterr = 0; /* Disable `getopt_long_only()` printing errors */
while (no_getopt_error) {
while (1) {
enum options {
OPT_ADDONGAP = 128, OPT_BATCH, OPT_BINARY, OPT_BG, OPT_BIND, OPT_BIND_TOP, OPT_BOLD, OPT_BORDER, OPT_BOX,
OPT_CMYK, OPT_COLS, OPT_COMPLIANTHEIGHT, OPT_DIRECT, OPT_DMRE, OPT_DOTSIZE, OPT_DOTTY, OPT_DUMP,
@ -1196,7 +1195,6 @@ int main(int argc, char **argv) {
OPT_SEPARATOR, OPT_SMALL, OPT_SQUARE, OPT_STRUCTAPP, OPT_TEXTGAP,
OPT_VERBOSE, OPT_VERS, OPT_VWHITESP, OPT_WERROR
};
int option_index = 0;
static const struct option long_options[] = {
{"addongap", 1, NULL, OPT_ADDONGAP},
{"barcode", 1, NULL, 'b'},
@ -1279,7 +1277,7 @@ int main(int argc, char **argv) {
{"whitesp", 1, NULL, 'w'},
{NULL, 0, NULL, 0}
};
const int c = getopt_long_only(argc, argv, "b:d:ehi:o:rtvw:", long_options, &option_index);
const int c = getopt_long_only(argc, argv, "b:d:ehi:o:rtvw:", long_options, NULL);
if (c == -1) break;
switch (c) {