doc: reference fmt(1) from fold(1)
[coreutils.git] / gl / lib / mbsalign.c
blob38face21dfb346abdd3aac01be794d390331e455
1 /* Align/Truncate a string in a given screen width
2 Copyright (C) 2009-2022 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 <https://www.gnu.org/licenses/>. */
17 /* Written by Pádraig Brady. */
19 #include <config.h>
20 #include "mbsalign.h"
22 #include "minmax.h"
24 #include <stdlib.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <stdint.h>
28 #include <stdbool.h>
29 #include <limits.h>
30 #include <wchar.h>
31 #include <wctype.h>
33 /* Replace non printable chars.
34 Note \t and \n etc. are non printable.
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 /* Write N_SPACES space characters to DEST while ensuring
81 nothing is written beyond DEST_END. A terminating NUL
82 is always added to DEST.
83 A pointer to the terminating NUL is returned. */
85 static char *
86 mbs_align_pad (char *dest, char const *dest_end, size_t n_spaces)
88 /* FIXME: Should we pad with "figure space" (\u2007)
89 if non ascii data present? */
90 while (n_spaces-- && (dest < dest_end))
91 *dest++ = ' ';
92 *dest = '\0';
93 return dest;
96 /* Align a string, SRC, in a field of *WIDTH columns, handling multi-byte
97 characters; write the result into the DEST_SIZE-byte buffer, DEST.
98 ALIGNMENT specifies whether to left- or right-justify or to center.
99 If SRC requires more than *WIDTH columns, truncate it to fit.
100 When centering, the number of trailing spaces may be one less than the
101 number of leading spaces.
102 Return the length in bytes required for the final result, not counting
103 the trailing NUL. A return value of DEST_SIZE or larger means there
104 wasn't enough space. DEST will be NUL terminated in any case.
105 Return SIZE_MAX upon error (invalid multi-byte sequence in SRC,
106 or malloc failure), unless MBA_UNIBYTE_FALLBACK is specified.
107 Update *WIDTH to indicate how many columns were used before padding. */
109 size_t
110 mbsalign (char const *src, char *dest, size_t dest_size,
111 size_t *width, mbs_align_t align, int flags)
113 size_t ret = SIZE_MAX;
114 size_t src_size = strlen (src) + 1;
115 char *newstr = NULL;
116 wchar_t *str_wc = NULL;
117 char const *str_to_print = src;
118 size_t n_cols = src_size - 1;
119 size_t n_used_bytes = n_cols; /* Not including NUL */
120 size_t n_spaces = 0;
121 bool conversion = false;
122 bool wc_enabled = false;
124 /* In multi-byte locales convert to wide characters
125 to allow easy truncation. Also determine number
126 of screen columns used. */
127 if (!(flags & MBA_UNIBYTE_ONLY) && MB_CUR_MAX > 1)
129 size_t src_chars = mbstowcs (NULL, src, 0);
130 if (src_chars == SIZE_MAX)
132 if (flags & MBA_UNIBYTE_FALLBACK)
133 goto mbsalign_unibyte;
134 else
135 goto mbsalign_cleanup;
137 src_chars += 1; /* make space for NUL */
138 str_wc = malloc (src_chars * sizeof (wchar_t));
139 if (str_wc == NULL)
141 if (flags & MBA_UNIBYTE_FALLBACK)
142 goto mbsalign_unibyte;
143 else
144 goto mbsalign_cleanup;
146 if (mbstowcs (str_wc, src, src_chars) != 0)
148 str_wc[src_chars - 1] = L'\0';
149 wc_enabled = true;
150 conversion = wc_ensure_printable (str_wc);
151 n_cols = wcswidth (str_wc, src_chars);
155 /* If we transformed or need to truncate the source string
156 then create a modified copy of it. */
157 if (wc_enabled && (conversion || (n_cols > *width)))
159 if (conversion)
161 /* May have increased the size by converting
162 \t to \uFFFD for example. */
163 src_size = wcstombs (NULL, str_wc, 0) + 1;
165 newstr = malloc (src_size);
166 if (newstr == NULL)
168 if (flags & MBA_UNIBYTE_FALLBACK)
169 goto mbsalign_unibyte;
170 else
171 goto mbsalign_cleanup;
173 str_to_print = newstr;
174 n_cols = wc_truncate (str_wc, *width);
175 n_used_bytes = wcstombs (newstr, str_wc, src_size);
178 mbsalign_unibyte:
180 if (n_cols > *width) /* Unibyte truncation required. */
182 n_cols = *width;
183 n_used_bytes = n_cols;
186 if (*width > n_cols) /* Padding required. */
187 n_spaces = *width - n_cols;
189 /* indicate to caller how many cells needed (not including padding). */
190 *width = n_cols;
193 size_t start_spaces, end_spaces;
195 switch (align)
197 case MBS_ALIGN_LEFT:
198 start_spaces = 0;
199 end_spaces = n_spaces;
200 break;
201 case MBS_ALIGN_RIGHT:
202 start_spaces = n_spaces;
203 end_spaces = 0;
204 break;
205 case MBS_ALIGN_CENTER:
206 default:
207 start_spaces = n_spaces / 2 + n_spaces % 2;
208 end_spaces = n_spaces / 2;
209 break;
212 if (flags & MBA_NO_LEFT_PAD)
213 start_spaces = 0;
214 if (flags & MBA_NO_RIGHT_PAD)
215 end_spaces = 0;
217 /* Write as much NUL terminated output to DEST as possible. */
218 if (dest_size != 0)
220 size_t space_left;
221 char *dest_end = dest + dest_size - 1;
223 dest = mbs_align_pad (dest, dest_end, start_spaces);
224 space_left = dest_end - dest;
225 dest = mempcpy (dest, str_to_print, MIN (n_used_bytes, space_left));
226 mbs_align_pad (dest, dest_end, end_spaces);
229 /* indicate to caller how many bytes needed (not including NUL). */
230 ret = n_used_bytes + ((start_spaces + end_spaces) * 1);
233 mbsalign_cleanup:
235 free (str_wc);
236 free (newstr);
238 return ret;
241 /* A wrapper around mbsalign() to dynamically allocate the
242 minimum amount of memory to store the result.
243 Return NULL on failure. */
245 char *
246 ambsalign (char const *src, size_t *width, mbs_align_t align, int flags)
248 size_t orig_width = *width;
249 size_t size = *width; /* Start with enough for unibyte mode. */
250 size_t req = size;
251 char *buf = NULL;
253 while (req >= size)
255 char *nbuf;
256 size = req + 1; /* Space for NUL. */
257 nbuf = realloc (buf, size);
258 if (nbuf == NULL)
260 free (buf);
261 buf = NULL;
262 break;
264 buf = nbuf;
265 *width = orig_width;
266 req = mbsalign (src, buf, size, width, align, flags);
267 if (req == SIZE_MAX)
269 free (buf);
270 buf = NULL;
271 break;
275 return buf;