tests: use "compare exp out", not "compare out exp"
[coreutils/ericb.git] / gl / lib / mbsalign.c
blobef45c35af47d3386cda013f6ed42157adbcfb583
1 /* Align/Truncate a string in a given screen width
2 Copyright (C) 2009-2011 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
17 /* Written by Pádraig Brady. */
19 #include <config.h>
20 #include "mbsalign.h"
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stdio.h>
25 #include <stdint.h>
26 #include <stdbool.h>
27 #include <limits.h>
28 #include <wchar.h>
29 #include <wctype.h>
31 #ifndef MIN
32 # define MIN(a, b) ((a) < (b) ? (a) : (b))
33 #endif
35 /* Replace non printable chars.
36 Note \t and \n etc. are non printable.
37 Return 1 if replacement made, 0 otherwise. */
39 static bool
40 wc_ensure_printable (wchar_t *wchars)
42 bool replaced = false;
43 wchar_t *wc = wchars;
44 while (*wc)
46 if (!iswprint ((wint_t) *wc))
48 *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
49 replaced = true;
51 wc++;
53 return replaced;
56 /* Truncate wchar string to width cells.
57 * Returns number of cells used. */
59 static size_t
60 wc_truncate (wchar_t *wc, size_t width)
62 size_t cells = 0;
63 int next_cells = 0;
65 while (*wc)
67 next_cells = wcwidth (*wc);
68 if (next_cells == -1) /* non printable */
70 *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
71 next_cells = 1;
73 if (cells + next_cells > width)
74 break;
75 cells += next_cells;
76 wc++;
78 *wc = L'\0';
79 return cells;
82 /* Write N_SPACES space characters to DEST while ensuring
83 nothing is written beyond DEST_END. A terminating NUL
84 is always added to DEST.
85 A pointer to the terminating NUL is returned. */
87 static char*
88 mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces)
90 /* FIXME: Should we pad with "figure space" (\u2007)
91 if non ascii data present? */
92 while (n_spaces-- && (dest < dest_end))
93 *dest++ = ' ';
94 *dest = '\0';
95 return dest;
98 /* Align a string, SRC, in a field of *WIDTH columns, handling multi-byte
99 characters; write the result into the DEST_SIZE-byte buffer, DEST.
100 ALIGNMENT specifies whether to left- or right-justify or to center.
101 If SRC requires more than *WIDTH columns, truncate it to fit.
102 When centering, the number of trailing spaces may be one less than the
103 number of leading spaces.
104 Return the length in bytes required for the final result, not counting
105 the trailing NUL. A return value of DEST_SIZE or larger means there
106 wasn't enough space. DEST will be NUL terminated in any case.
107 Return SIZE_MAX upon error (invalid multi-byte sequence in SRC,
108 or malloc failure), unless MBA_UNIBYTE_FALLBACK is specified.
109 Update *WIDTH to indicate how many columns were used before padding. */
111 size_t
112 mbsalign (const char *src, char *dest, size_t dest_size,
113 size_t *width, mbs_align_t align, int flags)
115 size_t ret = SIZE_MAX;
116 size_t src_size = strlen (src) + 1;
117 char *newstr = NULL;
118 wchar_t *str_wc = NULL;
119 const char *str_to_print = src;
120 size_t n_cols = src_size - 1;
121 size_t n_used_bytes = n_cols; /* Not including NUL */
122 size_t n_spaces = 0;
123 bool conversion = false;
124 bool wc_enabled = false;
126 /* In multi-byte locales convert to wide characters
127 to allow easy truncation. Also determine number
128 of screen columns used. */
129 if (MB_CUR_MAX > 1)
131 size_t src_chars = mbstowcs (NULL, src, 0);
132 if (src_chars == SIZE_MAX)
134 if (flags & MBA_UNIBYTE_FALLBACK)
135 goto mbsalign_unibyte;
136 else
137 goto mbsalign_cleanup;
139 src_chars += 1; /* make space for NUL */
140 str_wc = malloc (src_chars * sizeof (wchar_t));
141 if (str_wc == NULL)
143 if (flags & MBA_UNIBYTE_FALLBACK)
144 goto mbsalign_unibyte;
145 else
146 goto mbsalign_cleanup;
148 if (mbstowcs (str_wc, src, src_chars) != 0)
150 str_wc[src_chars - 1] = L'\0';
151 wc_enabled = true;
152 conversion = wc_ensure_printable (str_wc);
153 n_cols = wcswidth (str_wc, src_chars);
157 /* If we transformed or need to truncate the source string
158 then create a modified copy of it. */
159 if (wc_enabled && (conversion || (n_cols > *width)))
161 if (conversion)
163 /* May have increased the size by converting
164 \t to \uFFFD for example. */
165 src_size = wcstombs (NULL, str_wc, 0) + 1;
167 newstr = malloc (src_size);
168 if (newstr == NULL)
170 if (flags & MBA_UNIBYTE_FALLBACK)
171 goto mbsalign_unibyte;
172 else
173 goto mbsalign_cleanup;
175 str_to_print = newstr;
176 n_cols = wc_truncate (str_wc, *width);
177 n_used_bytes = wcstombs (newstr, str_wc, src_size);
180 mbsalign_unibyte:
182 if (n_cols > *width) /* Unibyte truncation required. */
184 n_cols = *width;
185 n_used_bytes = n_cols;
188 if (*width > n_cols) /* Padding required. */
189 n_spaces = *width - n_cols;
191 /* indicate to caller how many cells needed (not including padding). */
192 *width = n_cols;
194 /* indicate to caller how many bytes needed (not including NUL). */
195 ret = n_used_bytes + (n_spaces * 1);
197 /* Write as much NUL terminated output to DEST as possible. */
198 if (dest_size != 0)
200 size_t start_spaces, end_spaces, space_left;
201 char *dest_end = dest + dest_size - 1;
203 switch (align)
205 case MBS_ALIGN_LEFT:
206 start_spaces = 0;
207 end_spaces = n_spaces;
208 break;
209 case MBS_ALIGN_RIGHT:
210 start_spaces = n_spaces;
211 end_spaces = 0;
212 break;
213 case MBS_ALIGN_CENTER:
214 default:
215 start_spaces = n_spaces / 2 + n_spaces % 2;
216 end_spaces = n_spaces / 2;
217 break;
220 dest = mbs_align_pad (dest, dest_end, start_spaces);
221 space_left = dest_end - dest;
222 dest = mempcpy (dest, str_to_print, MIN (n_used_bytes, space_left));
223 mbs_align_pad (dest, dest_end, end_spaces);
226 mbsalign_cleanup:
228 free (str_wc);
229 free (newstr);
231 return ret;
234 /* A wrapper around mbsalign() to dynamically allocate the
235 minimum amount of memory to store the result.
236 Return NULL on failure. */
238 char *
239 ambsalign (const char *src, size_t *width, mbs_align_t align, int flags)
241 size_t orig_width = *width;
242 size_t size = *width; /* Start with enough for unibyte mode. */
243 size_t req = size;
244 char *buf = NULL;
246 while (req >= size)
248 char *nbuf;
249 size = req + 1; /* Space for NUL. */
250 nbuf = realloc (buf, size);
251 if (nbuf == NULL)
253 free (buf);
254 buf = NULL;
255 break;
257 buf = nbuf;
258 *width = orig_width;
259 req = mbsalign (src, buf, size, width, align, flags);
260 if (req == SIZE_MAX)
262 free (buf);
263 buf = NULL;
264 break;
268 return buf;