maint: remove Local Variables: indent-tabs-mode: nil from all sources
[coreutils.git] / gl / lib / mbsalign.c
blob0dfda75973e20da357fe7c303a6a31da1dee93e3
1 /* Align/Truncate a string in a given screen width
2 Copyright (C) 2009 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 <stdbool.h>
26 #include <limits.h>
27 #include <wchar.h>
28 #include <wctype.h>
30 #ifndef MIN
31 # define MIN(a, b) ((a) < (b) ? (a) : (b))
32 #endif
34 /* Replace non printable chars.
35 Return 1 if replacement made, 0 otherwise. */
37 static bool
38 wc_ensure_printable (wchar_t *wchars)
40 bool replaced = false;
41 wchar_t *wc = wchars;
42 while (*wc)
44 if (!iswprint ((wint_t) *wc))
46 *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
47 replaced = true;
49 wc++;
51 return replaced;
54 /* Truncate wchar string to width cells.
55 * Returns number of cells used. */
57 static size_t
58 wc_truncate (wchar_t *wc, size_t width)
60 size_t cells = 0;
61 int next_cells = 0;
63 while (*wc)
65 next_cells = wcwidth (*wc);
66 if (next_cells == -1) /* non printable */
68 *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
69 next_cells = 1;
71 if (cells + next_cells > width)
72 break;
73 cells += next_cells;
74 wc++;
76 *wc = L'\0';
77 return cells;
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 */
83 static int
84 rpl_wcswidth (const wchar_t *s, size_t n)
86 int ret = 0;
88 while (n-- > 0 && *s != L'\0')
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;
98 return ret;
101 /* Write N_SPACES space characters to DEST while ensuring
102 nothing is written beyond DEST_END. A terminating NUL
103 is always added to DEST.
104 A pointer to the terminating NUL is returned. */
106 static char*
107 mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces)
109 /* FIXME: Should we pad with "figure space" (\u2007)
110 if non ascii data present? */
111 while (n_spaces-- && (dest < dest_end))
112 *dest++ = ' ';
113 *dest = '\0';
114 return dest;
117 /* Align a string, SRC, in a field of *WIDTH columns, handling multi-byte
118 characters; write the result into the DEST_SIZE-byte buffer, DEST.
119 ALIGNMENT specifies whether to left- or right-justify or to center.
120 If SRC requires more than *WIDTH columns, truncate it to fit.
121 When centering, the number of trailing spaces may be one less than the
122 number of leading spaces. The FLAGS parameter is unused at present.
123 Return the length in bytes required for the final result, not counting
124 the trailing NUL. A return value of DEST_SIZE or larger means there
125 wasn't enough space. DEST will be NUL terminated in any case.
126 Return (size_t) -1 upon error (invalid multi-byte sequence in SRC,
127 or malloc failure).
128 Update *WIDTH to indicate how many columns were used before padding. */
130 size_t
131 mbsalign (const char *src, char *dest, size_t dest_size,
132 size_t *width, mbs_align_t align, int flags)
134 size_t ret = -1;
135 size_t src_size = strlen (src) + 1;
136 char *newstr = NULL;
137 wchar_t *str_wc = NULL;
138 const char *str_to_print = src;
139 size_t n_cols = src_size - 1;
140 size_t n_used_bytes = n_cols; /* Not including NUL */
141 size_t n_spaces = 0;
142 bool conversion = false;
143 bool wc_enabled = false;
145 /* In multi-byte locales convert to wide characters
146 to allow easy truncation. Also determine number
147 of screen columns used. */
148 if (MB_CUR_MAX > 1)
150 size_t src_chars = mbstowcs (NULL, src, 0);
151 if (src_chars == (size_t) -1)
152 goto mbsalign_cleanup;
153 src_chars += 1; /* make space for NUL */
154 str_wc = malloc (src_chars * sizeof (wchar_t));
155 if (str_wc == NULL)
156 goto mbsalign_cleanup;
157 if (mbstowcs (str_wc, src, src_chars) > 0)
159 str_wc[src_chars - 1] = L'\0';
160 wc_enabled = true;
161 conversion = wc_ensure_printable (str_wc);
162 n_cols = rpl_wcswidth (str_wc, src_chars);
166 /* If we transformed or need to truncate the source string
167 then create a modified copy of it. */
168 if (conversion || (n_cols > *width))
170 newstr = malloc (src_size);
171 if (newstr == NULL)
172 goto mbsalign_cleanup;
173 str_to_print = newstr;
174 if (wc_enabled)
176 n_cols = wc_truncate (str_wc, *width);
177 n_used_bytes = wcstombs (newstr, str_wc, src_size);
179 else
181 n_cols = *width;
182 n_used_bytes = n_cols;
183 memcpy (newstr, src, n_cols);
184 newstr[n_cols] = '\0';
188 if (*width > n_cols)
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 char *dest_end = dest + dest_size - 1;
201 size_t start_spaces = n_spaces / 2 + n_spaces % 2;
202 size_t end_spaces = n_spaces / 2;
204 switch (align)
206 case MBS_ALIGN_CENTER:
207 start_spaces = n_spaces / 2 + n_spaces % 2;
208 end_spaces = n_spaces / 2;
209 break;
210 case MBS_ALIGN_LEFT:
211 start_spaces = 0;
212 end_spaces = n_spaces;
213 break;
214 case MBS_ALIGN_RIGHT:
215 start_spaces = n_spaces;
216 end_spaces = 0;
217 break;
220 dest = mbs_align_pad (dest, dest_end, start_spaces);
221 dest = mempcpy(dest, str_to_print, MIN (n_used_bytes, dest_end - dest));
222 dest = mbs_align_pad (dest, dest_end, end_spaces);
225 mbsalign_cleanup:
227 free (str_wc);
228 free (newstr);
230 return ret;