1 /* Align/Truncate a string in a given screen width
2 Copyright (C) 2009-2012 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. */
32 # define MIN(a, b) ((a) < (b) ? (a) : (b))
35 /* Replace non printable chars.
36 Note \t and \n etc. are non printable.
37 Return 1 if replacement made, 0 otherwise. */
40 wc_ensure_printable (wchar_t *wchars
)
42 bool replaced
= false;
46 if (!iswprint ((wint_t) *wc
))
48 *wc
= 0xFFFD; /* L'\uFFFD' (replacement char) */
56 /* Truncate wchar string to width cells.
57 * Returns number of cells used. */
60 wc_truncate (wchar_t *wc
, size_t width
)
67 next_cells
= wcwidth (*wc
);
68 if (next_cells
== -1) /* non printable */
70 *wc
= 0xFFFD; /* L'\uFFFD' (replacement char) */
73 if (cells
+ next_cells
> width
)
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. */
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
))
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. */
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;
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 */
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. */
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
;
137 goto mbsalign_cleanup
;
139 src_chars
+= 1; /* make space for NUL */
140 str_wc
= malloc (src_chars
* sizeof (wchar_t));
143 if (flags
& MBA_UNIBYTE_FALLBACK
)
144 goto mbsalign_unibyte
;
146 goto mbsalign_cleanup
;
148 if (mbstowcs (str_wc
, src
, src_chars
) != 0)
150 str_wc
[src_chars
- 1] = L
'\0';
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
)))
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
);
170 if (flags
& MBA_UNIBYTE_FALLBACK
)
171 goto mbsalign_unibyte
;
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
);
182 if (n_cols
> *width
) /* Unibyte truncation required. */
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). */
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. */
200 size_t start_spaces
, end_spaces
, space_left
;
201 char *dest_end
= dest
+ dest_size
- 1;
207 end_spaces
= n_spaces
;
209 case MBS_ALIGN_RIGHT
:
210 start_spaces
= n_spaces
;
213 case MBS_ALIGN_CENTER
:
215 start_spaces
= n_spaces
/ 2 + n_spaces
% 2;
216 end_spaces
= n_spaces
/ 2;
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
);
234 /* A wrapper around mbsalign() to dynamically allocate the
235 minimum amount of memory to store the result.
236 Return NULL on failure. */
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. */
249 size
= req
+ 1; /* Space for NUL. */
250 nbuf
= realloc (buf
, size
);
259 req
= mbsalign (src
, buf
, size
, width
, align
, flags
);