blob: ea6851f29674b2fa09d9372da6db10394a735daf [file] [log] [blame]
bigbiff bigbiffe60683a2013-02-22 20:55:50 -05001/* Align/Truncate a string in a given screen width
2 Copyright (C) 2009-2010 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>. */
16
17/* Written by Pádraig Brady. */
18
19#include <config.h>
20
21#include <stdlib.h>
22#include <string.h>
23#include <stdio.h>
24#include <stdbool.h>
25#include <limits.h>
26
27#include "c.h"
28#include "mbsalign.h"
29#include "widechar.h"
30
31
32#ifdef HAVE_WIDECHAR
33/* Replace non printable chars.
34 Note \t and \n etc. are non printable.
35 Return 1 if replacement made, 0 otherwise. */
36
37static bool
38wc_ensure_printable (wchar_t *wchars)
39{
40 bool replaced = false;
41 wchar_t *wc = wchars;
42 while (*wc)
43 {
44 if (!iswprint ((wint_t) *wc))
45 {
46 *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
47 replaced = true;
48 }
49 wc++;
50 }
51 return replaced;
52}
53
54/* Truncate wchar string to width cells.
55 * Returns number of cells used. */
56
57static size_t
58wc_truncate (wchar_t *wc, size_t width)
59{
60 size_t cells = 0;
61 int next_cells = 0;
62
63 while (*wc)
64 {
65 next_cells = wcwidth (*wc);
66 if (next_cells == -1) /* non printable */
67 {
68 *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
69 next_cells = 1;
70 }
71 if (cells + next_cells > width)
72 break;
73 cells += next_cells;
74 wc++;
75 }
76 *wc = L'\0';
77 return cells;
78}
79
80/* FIXME: move this function to gnulib as it's missing on:
81 OpenBSD 3.8, IRIX 5.3, Solaris 2.5.1, mingw, BeOS */
82
83static int
84rpl_wcswidth (const wchar_t *s, size_t n)
85{
86 int ret = 0;
87
88 while (n-- > 0 && *s != L'\0')
89 {
90 int nwidth = wcwidth (*s++);
91 if (nwidth == -1) /* non printable */
92 return -1;
93 if (ret > (INT_MAX - nwidth)) /* overflow */
94 return -1;
95 ret += nwidth;
96 }
97
98 return ret;
99}
100#endif
101
102/* Truncate multi-byte string to @width and returns number of
103 * bytes of the new string @str, and in @width returns number
104 * of cells.
105 */
106size_t
107mbs_truncate(char *str, size_t *width)
108{
109 ssize_t bytes = strlen(str);
110#ifdef HAVE_WIDECHAR
111 ssize_t sz = mbstowcs(NULL, str, 0);
112 wchar_t *wcs = NULL;
113
114 if (sz == (ssize_t) -1)
115 goto done;
116
117 wcs = malloc((sz + 1) * sizeof(wchar_t));
118 if (!wcs)
119 goto done;
120
121 if (!mbstowcs(wcs, str, sz))
122 goto done;
123 *width = wc_truncate(wcs, *width);
124 bytes = wcstombs(str, wcs, bytes);
125done:
126 free(wcs);
127#else
128 if (*width < bytes)
129 bytes = *width;
130#endif
131 if (bytes >= 0)
132 str[bytes] = '\0';
133 return bytes;
134}
135
136/* Write N_SPACES space characters to DEST while ensuring
137 nothing is written beyond DEST_END. A terminating NUL
138 is always added to DEST.
139 A pointer to the terminating NUL is returned. */
140
141static char*
142mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces)
143{
144 /* FIXME: Should we pad with "figure space" (\u2007)
145 if non ascii data present? */
146 while (n_spaces-- && (dest < dest_end))
147 *dest++ = ' ';
148 *dest = '\0';
149 return dest;
150}
151
152/* Align a string, SRC, in a field of *WIDTH columns, handling multi-byte
153 characters; write the result into the DEST_SIZE-byte buffer, DEST.
154 ALIGNMENT specifies whether to left- or right-justify or to center.
155 If SRC requires more than *WIDTH columns, truncate it to fit.
156 When centering, the number of trailing spaces may be one less than the
157 number of leading spaces. The FLAGS parameter is unused at present.
158 Return the length in bytes required for the final result, not counting
159 the trailing NUL. A return value of DEST_SIZE or larger means there
160 wasn't enough space. DEST will be NUL terminated in any case.
161 Return (size_t) -1 upon error (invalid multi-byte sequence in SRC,
162 or malloc failure), unless MBA_UNIBYTE_FALLBACK is specified.
163 Update *WIDTH to indicate how many columns were used before padding. */
164
165size_t
166mbsalign (const char *src, char *dest, size_t dest_size,
167 size_t *width, mbs_align_t align, int flags)
168{
169 size_t ret = -1;
170 size_t src_size = strlen (src) + 1;
171 char *newstr = NULL;
172 wchar_t *str_wc = NULL;
173 const char *str_to_print = src;
174 size_t n_cols = src_size - 1;
175 size_t n_used_bytes = n_cols; /* Not including NUL */
176 size_t n_spaces = 0;
177 bool conversion = false;
178 bool wc_enabled = false;
179
180#ifdef HAVE_WIDECHAR
181 /* In multi-byte locales convert to wide characters
182 to allow easy truncation. Also determine number
183 of screen columns used. */
184 if (MB_CUR_MAX > 1)
185 {
186 size_t src_chars = mbstowcs (NULL, src, 0);
187 if (src_chars == (size_t) -1)
188 {
189 if (flags & MBA_UNIBYTE_FALLBACK)
190 goto mbsalign_unibyte;
191 else
192 goto mbsalign_cleanup;
193 }
194 src_chars += 1; /* make space for NUL */
195 str_wc = malloc (src_chars * sizeof (wchar_t));
196 if (str_wc == NULL)
197 {
198 if (flags & MBA_UNIBYTE_FALLBACK)
199 goto mbsalign_unibyte;
200 else
201 goto mbsalign_cleanup;
202 }
203 if (mbstowcs (str_wc, src, src_chars) != 0)
204 {
205 str_wc[src_chars - 1] = L'\0';
206 wc_enabled = true;
207 conversion = wc_ensure_printable (str_wc);
208 n_cols = rpl_wcswidth (str_wc, src_chars);
209 }
210 }
211
212 /* If we transformed or need to truncate the source string
213 then create a modified copy of it. */
214 if (wc_enabled && (conversion || (n_cols > *width)))
215 {
216 if (conversion)
217 {
218 /* May have increased the size by converting
219 \t to \uFFFD for example. */
220 src_size = wcstombs(NULL, str_wc, 0) + 1;
221 }
222 newstr = malloc (src_size);
223 if (newstr == NULL)
224 {
225 if (flags & MBA_UNIBYTE_FALLBACK)
226 goto mbsalign_unibyte;
227 else
228 goto mbsalign_cleanup;
229 }
230 str_to_print = newstr;
231 n_cols = wc_truncate (str_wc, *width);
232 n_used_bytes = wcstombs (newstr, str_wc, src_size);
233 }
234#endif
235
236mbsalign_unibyte:
237
238 if (n_cols > *width) /* Unibyte truncation required. */
239 {
240 n_cols = *width;
241 n_used_bytes = n_cols;
242 }
243
244 if (*width > n_cols) /* Padding required. */
245 n_spaces = *width - n_cols;
246
247 /* indicate to caller how many cells needed (not including padding). */
248 *width = n_cols;
249
250 /* indicate to caller how many bytes needed (not including NUL). */
251 ret = n_used_bytes + (n_spaces * 1);
252
253 /* Write as much NUL terminated output to DEST as possible. */
254 if (dest_size != 0)
255 {
256 char *dest_end = dest + dest_size - 1;
257 size_t start_spaces = n_spaces / 2 + n_spaces % 2;
258 size_t end_spaces = n_spaces / 2;
259
260 switch (align)
261 {
262 case MBS_ALIGN_CENTER:
263 start_spaces = n_spaces / 2 + n_spaces % 2;
264 end_spaces = n_spaces / 2;
265 break;
266 case MBS_ALIGN_LEFT:
267 start_spaces = 0;
268 end_spaces = n_spaces;
269 break;
270 case MBS_ALIGN_RIGHT:
271 start_spaces = n_spaces;
272 end_spaces = 0;
273 break;
274 default:
275 abort();
276 }
277
278 dest = mbs_align_pad (dest, dest_end, start_spaces);
279 size_t space_left = dest_end - dest;
280 //dest = mempcpy (dest, str_to_print, min (n_used_bytes, space_left));
281 memcpy (dest, str_to_print, min (n_used_bytes, space_left));
282 mbs_align_pad (dest, dest_end, end_spaces);
283 }
284
285mbsalign_cleanup:
286
287 free (str_wc);
288 free (newstr);
289
290 return ret;
291}