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. */
33 /* Replace non printable chars.
34 Note \t and \n etc. are non printable.
35 Return 1 if replacement made, 0 otherwise. */
38 wc_ensure_printable (wchar_t *wchars
)
40 bool replaced
= false;
44 if (!iswprint ((wint_t) *wc
))
46 *wc
= 0xFFFD; /* L'\uFFFD' (replacement char) */
54 /* Truncate wchar string to width cells.
55 * Returns number of cells used. */
58 wc_truncate (wchar_t *wc
, size_t width
)
65 next_cells
= wcwidth (*wc
);
66 if (next_cells
== -1) /* non printable */
68 *wc
= 0xFFFD; /* L'\uFFFD' (replacement char) */
71 if (cells
+ next_cells
> width
)
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. */
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
))
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. */
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;
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 */
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
;
135 goto mbsalign_cleanup
;
137 src_chars
+= 1; /* make space for NUL */
138 str_wc
= malloc (src_chars
* sizeof (wchar_t));
141 if (flags
& MBA_UNIBYTE_FALLBACK
)
142 goto mbsalign_unibyte
;
144 goto mbsalign_cleanup
;
146 if (mbstowcs (str_wc
, src
, src_chars
) != 0)
148 str_wc
[src_chars
- 1] = L
'\0';
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
)))
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
);
168 if (flags
& MBA_UNIBYTE_FALLBACK
)
169 goto mbsalign_unibyte
;
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
);
180 if (n_cols
> *width
) /* Unibyte truncation required. */
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). */
193 size_t start_spaces
, end_spaces
;
199 end_spaces
= n_spaces
;
201 case MBS_ALIGN_RIGHT
:
202 start_spaces
= n_spaces
;
205 case MBS_ALIGN_CENTER
:
207 start_spaces
= n_spaces
/ 2 + n_spaces
% 2;
208 end_spaces
= n_spaces
/ 2;
212 if (flags
& MBA_NO_LEFT_PAD
)
214 if (flags
& MBA_NO_RIGHT_PAD
)
217 /* Write as much NUL terminated output to DEST as possible. */
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);
241 /* A wrapper around mbsalign() to dynamically allocate the
242 minimum amount of memory to store the result.
243 Return NULL on failure. */
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. */
256 size
= req
+ 1; /* Space for NUL. */
257 nbuf
= realloc (buf
, size
);
266 req
= mbsalign (src
, buf
, size
, width
, align
, flags
);