diff options
-rw-r--r-- | .x-sc_program_name | 1 | ||||
-rw-r--r-- | gl/lib/mbsalign.c | 109 | ||||
-rw-r--r-- | gl/lib/mbsalign.h | 23 | ||||
-rw-r--r-- | gl/modules/mbsalign-tests | 11 | ||||
-rw-r--r-- | gl/tests/test-mbsalign.c | 93 |
5 files changed, 208 insertions, 29 deletions
diff --git a/.x-sc_program_name b/.x-sc_program_name index eb4855f59..ace3c8ff0 100644 --- a/.x-sc_program_name +++ b/.x-sc_program_name @@ -1,2 +1,3 @@ gl/lib/randint.c lib/euidaccess-stat.c +gl/tests/test-mbsalign.c diff --git a/gl/lib/mbsalign.c b/gl/lib/mbsalign.c index be259568d..5b55ec2d0 100644 --- a/gl/lib/mbsalign.c +++ b/gl/lib/mbsalign.c @@ -32,6 +32,7 @@ #endif /* Replace non printable chars. + Note \t and \n etc. are non printable. Return 1 if replacement made, 0 otherwise. */ static bool @@ -119,17 +120,17 @@ mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces) ALIGNMENT specifies whether to left- or right-justify or to center. If SRC requires more than *WIDTH columns, truncate it to fit. When centering, the number of trailing spaces may be one less than the - number of leading spaces. The FLAGS parameter is unused at present. + number of leading spaces. Return the length in bytes required for the final result, not counting the trailing NUL. A return value of DEST_SIZE or larger means there wasn't enough space. DEST will be NUL terminated in any case. Return (size_t) -1 upon error (invalid multi-byte sequence in SRC, - or malloc failure). + or malloc failure), unless MBA_UNIBYTE_FALLBACK is specified. Update *WIDTH to indicate how many columns were used before padding. */ size_t mbsalign (const char *src, char *dest, size_t dest_size, - size_t *width, mbs_align_t align, int flags _UNUSED_PARAMETER_) + size_t *width, mbs_align_t align, int flags) { size_t ret = -1; size_t src_size = strlen (src) + 1; @@ -149,12 +150,22 @@ mbsalign (const char *src, char *dest, size_t dest_size, { size_t src_chars = mbstowcs (NULL, src, 0); if (src_chars == (size_t) -1) - goto mbsalign_cleanup; + { + if (flags & MBA_UNIBYTE_FALLBACK) + goto mbsalign_unibyte; + else + goto mbsalign_cleanup; + } src_chars += 1; /* make space for NUL */ str_wc = malloc (src_chars * sizeof (wchar_t)); if (str_wc == NULL) - goto mbsalign_cleanup; - if (mbstowcs (str_wc, src, src_chars) > 0) + { + if (flags & MBA_UNIBYTE_FALLBACK) + goto mbsalign_unibyte; + else + goto mbsalign_cleanup; + } + if (mbstowcs (str_wc, src, src_chars) != 0) { str_wc[src_chars - 1] = L'\0'; wc_enabled = true; @@ -165,27 +176,30 @@ mbsalign (const char *src, char *dest, size_t dest_size, /* If we transformed or need to truncate the source string then create a modified copy of it. */ - if (conversion || (n_cols > *width)) + if (wc_enabled && (conversion || (n_cols > *width))) { - newstr = malloc (src_size); - if (newstr == NULL) - goto mbsalign_cleanup; - str_to_print = newstr; - if (wc_enabled) + newstr = malloc (src_size); + if (newstr == NULL) { - n_cols = wc_truncate (str_wc, *width); - n_used_bytes = wcstombs (newstr, str_wc, src_size); - } - else - { - n_cols = *width; - n_used_bytes = n_cols; - memcpy (newstr, src, n_cols); - newstr[n_cols] = '\0'; + if (flags & MBA_UNIBYTE_FALLBACK) + goto mbsalign_unibyte; + else + goto mbsalign_cleanup; } + str_to_print = newstr; + n_cols = wc_truncate (str_wc, *width); + n_used_bytes = wcstombs (newstr, str_wc, src_size); + } + +mbsalign_unibyte: + + if (n_cols > *width) /* Unibyte truncation required. */ + { + n_cols = *width; + n_used_bytes = n_cols; } - if (*width > n_cols) + if (*width > n_cols) /* Padding required. */ n_spaces = *width - n_cols; /* indicate to caller how many cells needed (not including padding). */ @@ -197,16 +211,11 @@ mbsalign (const char *src, char *dest, size_t dest_size, /* Write as much NUL terminated output to DEST as possible. */ if (dest_size != 0) { + size_t start_spaces, end_spaces; char *dest_end = dest + dest_size - 1; - size_t start_spaces = n_spaces / 2 + n_spaces % 2; - size_t end_spaces = n_spaces / 2; switch (align) { - case MBS_ALIGN_CENTER: - start_spaces = n_spaces / 2 + n_spaces % 2; - end_spaces = n_spaces / 2; - break; case MBS_ALIGN_LEFT: start_spaces = 0; end_spaces = n_spaces; @@ -215,10 +224,16 @@ mbsalign (const char *src, char *dest, size_t dest_size, start_spaces = n_spaces; end_spaces = 0; break; + case MBS_ALIGN_CENTER: + default: + start_spaces = n_spaces / 2 + n_spaces % 2; + end_spaces = n_spaces / 2; + break; } dest = mbs_align_pad (dest, dest_end, start_spaces); - dest = mempcpy(dest, str_to_print, MIN (n_used_bytes, dest_end - dest)); + size_t space_left = dest_end - dest; + dest = mempcpy (dest, str_to_print, MIN (n_used_bytes, space_left)); mbs_align_pad (dest, dest_end, end_spaces); } @@ -229,3 +244,39 @@ mbsalign_cleanup: return ret; } + +/* A wrapper around mbsalign() to dynamically allocate the + minimum amount of memory to store the result. + Return NULL on failure. */ + +char * +ambsalign (const char *src, size_t *width, mbs_align_t align, int flags) +{ + size_t orig_width = *width; + size_t size = *width; /* Start with enough for unibyte mode. */ + size_t req = size; + char *buf = NULL; + + while (req >= size) + { + size = req + 1; /* Space for NUL. */ + char *nbuf = realloc (buf, size); + if (nbuf == NULL) + { + free (buf); + buf = NULL; + break; + } + buf = nbuf; + *width = orig_width; + req = mbsalign (src, buf, size, width, align, flags); + if (req == (size_t) -1) + { + free (buf); + buf = NULL; + break; + } + } + + return buf; +} diff --git a/gl/lib/mbsalign.h b/gl/lib/mbsalign.h index a4ec69395..41bd49092 100644 --- a/gl/lib/mbsalign.h +++ b/gl/lib/mbsalign.h @@ -18,6 +18,29 @@ typedef enum { MBS_ALIGN_LEFT, MBS_ALIGN_RIGHT, MBS_ALIGN_CENTER } mbs_align_t; +enum { + /* Use unibyte mode for invalid multibyte strings or + or when heap memory is exhausted. */ + MBA_UNIBYTE_FALLBACK = 0x0001, + +#if 0 /* Other possible options. */ + /* Skip invalid multibyte chars rather than failing */ + MBA_IGNORE_INVALID = 0x0002, + + /* Align multibyte strings using "figure space" (\u2007) */ + MBA_USE_FIGURE_SPACE = 0x0004, + + /* Don't add any padding */ + MBA_TRUNCATE_ONLY = 0x0008, + + /* Don't truncate */ + MBA_PAD_ONLY = 0x0010, +#endif +}; + size_t mbsalign (const char *src, char *dest, size_t dest_size, size_t *width, mbs_align_t align, int flags); + +char * +ambsalign (const char *src, size_t *width, mbs_align_t align, int flags); diff --git a/gl/modules/mbsalign-tests b/gl/modules/mbsalign-tests new file mode 100644 index 000000000..8e0d138fe --- /dev/null +++ b/gl/modules/mbsalign-tests @@ -0,0 +1,11 @@ +Files: +tests/test-mbsalign.c +tests/macros.h + +Depends-on: + +configure.ac: + +Makefile.am: +TESTS += test-mbsalign +check_PROGRAMS += test-mbsalign diff --git a/gl/tests/test-mbsalign.c b/gl/tests/test-mbsalign.c new file mode 100644 index 000000000..9f8935732 --- /dev/null +++ b/gl/tests/test-mbsalign.c @@ -0,0 +1,93 @@ +/* Test that mbsalign works as advertised. + Copyright (C) 2010 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +/* Written by Pádraig Brady. */ + +#include <config.h> + +#include "mbsalign.h" +#include "macros.h" +#include <stdlib.h> +#include <locale.h> + +int +main (void) +{ + char dest[4 * 16 + 1]; + size_t width, n; + + /* Test unibyte truncation. */ + width = 4; + n = mbsalign ("t\tés", dest, sizeof dest, &width, MBS_ALIGN_LEFT, 0); + ASSERT (n == 4); + + /* Test center alignment. */ + width = 4; + n = mbsalign ("es", dest, sizeof dest, &width, MBS_ALIGN_CENTER, 0); + ASSERT (*dest == ' ' && *(dest + n - 1) == ' '); + + if (setlocale (LC_ALL, "en_US.UTF8")) + { + /* Check invalid input is flagged. */ + width = 4; + n = mbsalign ("t\xe1\xe2s", dest, sizeof dest, &width, MBS_ALIGN_LEFT, 0); + ASSERT (n == (size_t) -1); + + /* Check invalid input is treated as unibyte */ + width = 4; + n = mbsalign ("t\xe1\xe2s", dest, sizeof dest, &width, + MBS_ALIGN_LEFT, MBA_UNIBYTE_FALLBACK); + ASSERT (n == 4); + + /* Test multibyte center alignment. */ + width = 4; + n = mbsalign ("és", dest, sizeof dest, &width, MBS_ALIGN_CENTER, 0); + ASSERT (*dest == ' ' && *(dest + n - 1) == ' '); + + /* Test multibyte left alignment. */ + width = 4; + n = mbsalign ("és", dest, sizeof dest, &width, MBS_ALIGN_LEFT, 0); + ASSERT (*(dest + n - 1) == ' ' && *(dest + n - 2) == ' '); + + /* Test multibyte right alignment. */ + width = 4; + n = mbsalign ("és", dest, sizeof dest, &width, MBS_ALIGN_RIGHT, 0); + ASSERT (*(dest) == ' ' && *(dest + 1) == ' '); + + /* multibyte multicell truncation. */ + width = 4; /* cells */ + n = mbsalign ("日月火水", dest, sizeof dest, &width, + MBS_ALIGN_LEFT, 0); + ASSERT (n == 6); /* 2 characters */ + + /* multibyte unicell truncation. */ + width = 3; /* cells */ + n = mbsalign ("¹²³⁴", dest, sizeof dest, &width, MBS_ALIGN_LEFT, 0); + ASSERT (n == 6); /* 3 characters */ + + /* Check independence from dest buffer. */ + width = 4; /* cells */ + n = mbsalign ("¹²³⁴", dest, 0, &width, MBS_ALIGN_LEFT, 0); + ASSERT (n == 9); /* 4 characters */ + + /* Check that width is updated with cells required before padding. */ + width = 4; /* cells */ + n = mbsalign ("¹²³", dest, 0, &width, MBS_ALIGN_LEFT, 0); + ASSERT (width == 3); + } + + return 0; +} |