Coverage Report

Created: 2026-10-28 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/util-linux/lib/mbsalign.c
Line
Count
Source
1
/*
2
 * SPDX-License-Identifier: LGPL-2.1-or-later
3
 *
4
 * Align/Truncate a string in a given screen width
5
 * Copyright (C) 2009-2010 Free Software Foundation, Inc.
6
 *
7
 * This program is free software: you can redistribute it and/or modify it
8
 * under the terms of the GNU Lesser General Public License as published by the
9
 * Free Software Foundation, either version 2.1 of the License, or (at your
10
 * option) any later version.
11
 *
12
 * Written by Pádraig Brady.
13
 */
14
#include <stdlib.h>
15
#include <string.h>
16
#include <stdio.h>
17
#include <stdbool.h>
18
#include <limits.h>
19
#include <ctype.h>
20
21
#include "c.h"
22
#include "mbsalign.h"
23
#include "strutils.h"
24
#include "widechar.h"
25
26
/* Return the byte after a complete ANSI escape sequence, or NULL. */
27
static const char *skip_ansi_escape(const char *p, const char *end)
28
0
{
29
0
  const char *q;
30
31
0
  if (p >= end || (unsigned char) *p != '\033' || p + 1 >= end)
32
0
    return NULL;
33
34
0
  q = p + 1;
35
0
  if (*q == '[') {
36
0
    q++;
37
    /* CSI parameter bytes (0x30-0x3f) are followed by intermediate
38
     * bytes (0x20-0x2f) and a final byte (0x40-0x7e). None of them
39
     * occupies a cell.
40
     */
41
0
    while (q < end && (unsigned char) *q >= 0x30
42
0
        && (unsigned char) *q <= 0x3f)
43
0
      q++;
44
0
    while (q < end && (unsigned char) *q >= 0x20
45
0
        && (unsigned char) *q <= 0x2f)
46
0
      q++;
47
0
    if (q < end && (unsigned char) *q >= 0x40
48
0
        && (unsigned char) *q <= 0x7e)
49
0
      return q + 1;
50
0
  } else if (*q && strchr("()*+", *q) && q + 1 < end)
51
0
    return q + 2;
52
53
0
  return NULL;
54
0
}
55
56
/*
57
 * Counts number of cells in multibyte string. All control and
58
 * non-printable chars are ignored.
59
 *
60
 * Returns: number of cells.
61
 */
62
size_t mbs_nwidth(const char *buf, size_t bufsz)
63
0
{
64
0
  const char *p = buf, *last = buf;
65
0
  size_t width = 0;
66
67
0
#ifdef HAVE_WIDECHAR
68
0
  mbstate_t st;
69
0
  memset(&st, 0, sizeof(st));
70
0
#endif
71
0
  if (p && *p && bufsz)
72
0
    last = p + (bufsz - 1);
73
74
0
  while (p && *p && p <= last) {
75
0
    if (*p == '\033') {
76
0
      const char *e = skip_ansi_escape(p, last + 1);
77
78
0
      p = e ? e : p + 1;
79
0
      continue;
80
0
    }
81
0
    if (iscntrl((unsigned char) *p)) {
82
0
      p++;
83
0
      continue;
84
0
    }
85
0
#ifdef HAVE_WIDECHAR
86
0
    wchar_t wc;
87
0
    size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
88
89
0
    if (len == 0)
90
0
      break;
91
0
    if (len > 0 && iswprint(wc)) {
92
0
      int x = wcwidth(wc);
93
0
      if (x > 0)
94
0
        width += x;
95
0
    } else if (len == (size_t) -1 || len == (size_t) -2)
96
0
      len = 1;
97
0
    p += len;
98
#else
99
    if (isprint((unsigned char) *p))
100
      width++;
101
    p++;
102
#endif
103
0
  }
104
105
0
  return width;
106
0
}
107
108
size_t mbs_width(const char *s)
109
0
{
110
0
  if (!s || !*s)
111
0
    return 0;
112
0
  return mbs_nwidth(s, strlen(s));
113
0
}
114
115
/*
116
 * Counts number of cells in multibyte string. For all control and
117
 * non-printable chars is the result width enlarged to store \x?? hex
118
 * sequence. See mbs_safe_encode().
119
 *
120
 * Returns: number of cells, @sz returns number of bytes.
121
 */
122
size_t mbs_safe_nwidth(const char *buf, size_t bufsz, size_t *sz)
123
0
{
124
0
  const char *p = buf, *last = buf;
125
0
  size_t width = 0, bytes = 0;
126
127
0
#ifdef HAVE_WIDECHAR
128
0
  mbstate_t st;
129
0
  memset(&st, 0, sizeof(st));
130
0
#endif
131
0
  if (p && *p && bufsz)
132
0
    last = p + (bufsz - 1);
133
134
0
  while (p && *p && p <= last) {
135
0
    if ((p < last && *p == '\\' && *(p + 1) == 'x')
136
0
        || iscntrl((unsigned char) *p)) {
137
0
      width += 4, bytes += 4;   /* *p encoded to \x?? */
138
0
      p++;
139
0
    }
140
0
#ifdef HAVE_WIDECHAR
141
0
    else {
142
0
      wchar_t wc;
143
0
      size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
144
145
0
      if (len == 0)
146
0
        break;
147
148
0
      if (len == (size_t) -1 || len == (size_t) -2) {
149
0
        len = 1;
150
0
        if (isprint((unsigned char) *p))
151
0
          width += 1, bytes += 1;
152
0
        else
153
0
          width += 4, bytes += 4;
154
155
0
      } else if (!iswprint(wc)) {
156
0
        width += len * 4; /* hex encode whole sequence */
157
0
        bytes += len * 4;
158
0
      } else {
159
0
        width += wcwidth(wc); /* number of cells */
160
0
        bytes += len;   /* number of bytes */
161
0
      }
162
0
      p += len;
163
0
    }
164
#else
165
    else if (!isprint((unsigned char) *p)) {
166
      width += 4, bytes += 4;   /* *p encoded to \x?? */
167
      p++;
168
    } else {
169
      width++, bytes++;
170
      p++;
171
    }
172
#endif
173
0
  }
174
175
0
  if (sz)
176
0
    *sz = bytes;
177
0
  return width;
178
0
}
179
180
size_t mbs_safe_width(const char *s)
181
0
{
182
0
  if (!s || !*s)
183
0
    return 0;
184
0
  return mbs_safe_nwidth(s, strlen(s), NULL);
185
0
}
186
187
/*
188
 * Copy @s to @buf and replace control and non-printable chars with
189
 * \x?? hex sequence. The @width returns number of cells. The @safechars
190
 * are not encoded.
191
 *
192
 * The @buf has to be big enough to store mbs_safe_encode_size(strlen(s)))
193
 * bytes.
194
 */
195
char *mbs_safe_encode_to_buffer(const char *s, size_t *width, char *buf, size_t bufsiz, const char *safechars)
196
0
{
197
0
  const char *p = s;
198
0
  char *r;
199
0
  int rsz;
200
0
  size_t sz = s ? strlen(s) : 0;
201
202
0
#ifdef HAVE_WIDECHAR
203
0
  mbstate_t st;
204
0
  memset(&st, 0, sizeof(st));
205
0
#endif
206
0
  if (!sz || !buf || !bufsiz)
207
0
    return NULL;
208
209
0
  r = buf;
210
0
  rsz = (int) bufsiz;
211
0
  *width = 0;
212
213
0
  while (p && *p) {
214
0
    if (safechars && strchr(safechars, *p)) {
215
0
      if (rsz < 2)
216
0
        break;
217
0
      *r++ = *p++;
218
0
      rsz--;
219
0
      continue;
220
0
    }
221
222
0
    if ((*p == '\\' && *(p + 1) == 'x')
223
0
        || iscntrl((unsigned char) *p)) {
224
0
      if (rsz < 5)
225
0
        break;
226
0
      snprintf(r, rsz, "\\x%02x", (unsigned char) *p);
227
0
      r += 4;
228
0
      rsz -= 4;
229
0
      *width += 4;
230
0
      p++;
231
0
    }
232
0
#ifdef HAVE_WIDECHAR
233
0
    else {
234
0
      wchar_t wc;
235
0
      size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
236
237
0
      if (len == 0)
238
0
        break;   /* end of string */
239
240
0
      if (len == (size_t) -1 || len == (size_t) -2) {
241
0
        len = 1;
242
        /*
243
         * Not valid multibyte sequence -- maybe it's
244
         * printable char according to the current locales.
245
         */
246
0
        if (!isprint((unsigned char) *p)) {
247
0
          if (rsz < 5)
248
0
            break;
249
0
          snprintf(r, rsz, "\\x%02x", (unsigned char) *p);
250
0
          r += 4;
251
0
          rsz -= 4;
252
0
          *width += 4;
253
0
        } else {
254
0
          if (rsz < 2)
255
0
            break;
256
0
          (*width)++;
257
0
          *r++ = *p;
258
0
          rsz--;
259
0
        }
260
0
      } else if (!iswprint(wc)) {
261
0
        size_t i;
262
0
        for (i = 0; i < len; i++) {
263
0
          if (rsz < 5)
264
0
            break;
265
0
          snprintf(r, rsz, "\\x%02x", (unsigned char) p[i]);
266
0
          r += 4;
267
0
          rsz -= 4;
268
0
          *width += 4;
269
0
        }
270
0
        if (i < len)
271
0
          break;
272
0
      } else {
273
0
        if (rsz < (int)len + 1)
274
0
          break;
275
0
        memcpy(r, p, len);
276
0
        r += len;
277
0
        rsz -= len;
278
0
        *width += wcwidth(wc);
279
0
      }
280
0
      p += len;
281
0
    }
282
#else
283
    else if (!isprint((unsigned char) *p)) {
284
      if (rsz < 5)
285
        break;
286
      snprintf(r, rsz, "\\x%02x", (unsigned char) *p);
287
      p++;
288
      r += 4;
289
      rsz -= 4;
290
      *width += 4;
291
    } else {
292
      if (rsz < 2)
293
        break;
294
      *r++ = *p++;
295
      rsz--;
296
      (*width)++;
297
    }
298
#endif
299
0
  }
300
301
0
  *r = '\0';
302
0
  return buf;
303
0
}
304
305
/*
306
 * Copy @s to @buf and replace broken sequences to \x?? hex sequence. The
307
 * @width returns number of cells. The @safechars are not encoded.
308
 *
309
 * The @buf has to be big enough to store mbs_safe_encode_size(strlen(s)))
310
 * bytes.
311
 */
312
char *mbs_invalid_encode_to_buffer(const char *s, size_t *width, char *buf, size_t bufsiz)
313
0
{
314
0
  const char *p = s;
315
0
  char *r;
316
0
  int rsz;
317
0
  size_t sz = s ? strlen(s) : 0;
318
319
0
#ifdef HAVE_WIDECHAR
320
0
  mbstate_t st;
321
0
  memset(&st, 0, sizeof(st));
322
0
#endif
323
0
  if (!sz || !buf || !bufsiz)
324
0
    return NULL;
325
326
0
  r = buf;
327
0
  rsz = (int) bufsiz;
328
0
  *width = 0;
329
330
0
  while (p && *p) {
331
0
#ifdef HAVE_WIDECHAR
332
0
    wchar_t wc;
333
0
    size_t len = mbrtowc(&wc, p, MB_CUR_MAX, &st);
334
#else
335
    size_t len = 1;
336
#endif
337
338
0
    if (len == 0)
339
0
      break;   /* end of string */
340
341
0
    if (len == (size_t) -1 || len == (size_t) -2) {
342
0
      len = 1;
343
      /*
344
       * Not valid multibyte sequence -- maybe it's
345
       * printable char according to the current locales.
346
       */
347
0
      if (!isprint((unsigned char) *p)) {
348
0
        if (rsz < 5)
349
0
          break;
350
0
        snprintf(r, rsz, "\\x%02x", (unsigned char) *p);
351
0
        r += 4;
352
0
        rsz -= 4;
353
0
        *width += 4;
354
0
      } else {
355
0
        if (rsz < 2)
356
0
          break;
357
0
        (*width)++;
358
0
        *r++ = *p;
359
0
        rsz--;
360
0
      }
361
0
    } else if (*p == '\\' && *(p + 1) == 'x') {
362
0
      if (rsz < 5)
363
0
        break;
364
0
      snprintf(r, rsz, "\\x%02x", (unsigned char) *p);
365
0
      r += 4;
366
0
      rsz -= 4;
367
0
      *width += 4;
368
0
    } else {
369
0
      if (rsz < (int)len + 1)
370
0
        break;
371
0
      r = mempcpy(r, p, len);
372
0
      rsz -= len;
373
0
      *width += wcwidth(wc);
374
0
    }
375
0
    p += len;
376
0
  }
377
378
0
  *r = '\0';
379
0
  return buf;
380
0
}
381
382
/*
383
 * Guess size
384
 */
385
size_t mbs_safe_encode_size(size_t bytes)
386
0
{
387
0
  return (bytes * 4) + 1;
388
0
}
389
390
/*
391
 * Count size of the original string in bytes (count \x?? as one byte)
392
 */
393
size_t mbs_safe_decode_size(const char *p)
394
0
{
395
0
  size_t bytes = 0;
396
397
0
  while (p && *p) {
398
0
    if (*p == '\\' && *(p + 1) == 'x' &&
399
0
        isxdigit(*(p + 2)) && isxdigit(*(p + 3)))
400
0
      p += 4;
401
0
    else
402
0
      p++;
403
0
    bytes++;
404
0
  }
405
0
  return bytes;
406
0
}
407
408
/*
409
 * Returns allocated string where all control and non-printable chars are
410
 * replaced with \x?? hex sequence.
411
 */
412
char *mbs_safe_encode(const char *s, size_t *width)
413
0
{
414
0
  size_t sz = s ? strlen(s) : 0;
415
0
  size_t bufsz;
416
0
  char *buf, *ret = NULL;
417
418
0
  if (!sz)
419
0
    return NULL;
420
0
  bufsz = mbs_safe_encode_size(sz);
421
0
  buf = malloc(bufsz);
422
0
  if (buf)
423
0
    ret = mbs_safe_encode_to_buffer(s, width, buf, bufsz, NULL);
424
0
  if (!ret)
425
0
    free(buf);
426
0
  return ret;
427
0
}
428
429
/*
430
 * Returns allocated string where all broken widechars chars are
431
 * replaced with \x?? hex sequence.
432
 */
433
char *mbs_invalid_encode(const char *s, size_t *width)
434
0
{
435
0
  size_t sz = s ? strlen(s) : 0;
436
0
  size_t bufsz;
437
0
  char *buf, *ret = NULL;
438
439
0
  if (!sz)
440
0
    return NULL;
441
0
  bufsz = mbs_safe_encode_size(sz);
442
0
  buf = malloc(bufsz);
443
0
  if (buf)
444
0
    ret = mbs_invalid_encode_to_buffer(s, width, buf, bufsz);
445
0
  if (!ret)
446
0
    free(buf);
447
0
  return ret;
448
0
}
449
450
#ifdef HAVE_WIDECHAR
451
452
static bool
453
wc_ensure_printable (wchar_t *wchars)
454
0
{
455
0
  bool replaced = false;
456
0
  wchar_t *wc = wchars;
457
0
  while (*wc)
458
0
    {
459
0
      if (!iswprint ((wint_t) *wc))
460
0
        {
461
0
          *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
462
0
          replaced = true;
463
0
        }
464
0
      wc++;
465
0
    }
466
0
  return replaced;
467
0
}
468
469
/* Return the wchar after a complete ANSI escape sequence, or NULL. */
470
static wchar_t *
471
wc_skip_ansi_escape (wchar_t *wc)
472
0
{
473
0
  wchar_t *p = wc + 1;
474
475
0
  if (*wc != L'\033' || !*p)
476
0
    return NULL;
477
478
0
  if (*p == L'[')
479
0
    {
480
0
      p++;
481
0
      while (*p >= 0x30 && *p <= 0x3f)
482
0
        p++;
483
0
      while (*p >= 0x20 && *p <= 0x2f)
484
0
        p++;
485
0
      if (*p >= 0x40 && *p <= 0x7e)
486
0
        return p + 1;
487
0
    }
488
0
  else if ((*p == L'(' || *p == L')' || *p == L'*' || *p == L'+') && p[1])
489
0
    return p + 2;
490
491
0
  return NULL;
492
0
}
493
494
/* Truncate wchar string to width cells.
495
 * Returns number of cells used.  */
496
497
static size_t
498
wc_truncate (wchar_t *wc, size_t width)
499
0
{
500
0
  size_t cells = 0;
501
0
  int next_cells = 0;
502
503
0
  while (*wc)
504
0
    {
505
0
      wchar_t *after_escape = wc_skip_ansi_escape (wc);
506
0
      if (after_escape)
507
0
        {
508
0
          wc = after_escape;
509
0
          continue;
510
0
        }
511
512
0
      next_cells = wcwidth (*wc);
513
0
      if (next_cells == -1) /* non printable */
514
0
        {
515
0
          *wc = 0xFFFD; /* L'\uFFFD' (replacement char) */
516
0
          next_cells = 1;
517
0
        }
518
0
      if (cells + next_cells > width)
519
0
        break;
520
521
0
      cells += next_cells;
522
0
      wc++;
523
0
    }
524
0
  *wc = L'\0';
525
0
  return cells;
526
0
}
527
528
static int
529
rpl_wcswidth (const wchar_t *s, size_t n)
530
0
{
531
0
  int ret = 0;
532
533
0
  while (n-- > 0 && *s != L'\0')
534
0
    {
535
0
      int nwidth = wcwidth (*s++);
536
0
      if (nwidth == -1)             /* non printable */
537
0
        return -1;
538
0
      if (ret > (INT_MAX - nwidth)) /* overflow */
539
0
        return -1;
540
0
      ret += nwidth;
541
0
    }
542
543
0
  return ret;
544
0
}
545
#endif /* HAVE_WIDECHAR */
546
547
/* Truncate multi-byte string to @width and returns number of
548
 * bytes of the new string @str, and in @width returns number
549
 * of cells.
550
 */
551
size_t
552
mbs_truncate(char *str, size_t *width)
553
0
{
554
0
  ssize_t bytes = strlen(str);
555
0
#ifdef HAVE_WIDECHAR
556
0
  ssize_t sz = mbstowcs(NULL, str, 0);
557
0
  wchar_t *wcs = NULL;
558
559
0
  if (sz == (ssize_t) -1)
560
0
    goto done;
561
562
0
  wcs = calloc(1, (sz + 1) * sizeof(wchar_t));
563
0
  if (!wcs)
564
0
    goto done;
565
566
0
  if (!mbstowcs(wcs, str, sz))
567
0
    goto done;
568
0
  *width = wc_truncate(wcs, *width);
569
0
  bytes = wcstombs(str, wcs, bytes);
570
0
done:
571
0
  free(wcs);
572
#else
573
  {
574
    const char *p = str;
575
    const char *end = str + bytes;
576
    size_t cells = 0;
577
578
    while (p < end) {
579
      const char *after_escape = skip_ansi_escape(p, end);
580
581
      if (after_escape) {
582
        p = after_escape;
583
        continue;
584
      }
585
      if (cells >= *width)
586
        break;
587
      p++;
588
      cells++;
589
    }
590
    bytes = p - str;
591
    *width = cells;
592
  }
593
#endif
594
0
  if (bytes >= 0)
595
0
    str[bytes] = '\0';
596
0
  return bytes;
597
0
}
598
599
/* Write N_SPACES space characters to DEST while ensuring
600
   nothing is written beyond DEST_END. A terminating NUL
601
   is always added to DEST.
602
   A pointer to the terminating NUL is returned.  */
603
604
static char*
605
mbs_align_pad (char *dest, const char* dest_end, size_t n_spaces, int padchar)
606
0
{
607
0
  for (/* nothing */; n_spaces && (dest < dest_end); n_spaces--)
608
0
    *dest++ = padchar;
609
0
  *dest = '\0';
610
0
  return dest;
611
0
}
612
613
size_t
614
mbsalign (const char *src, char *dest, size_t dest_size,
615
          size_t *width, mbs_align_t align, int flags)
616
0
{
617
0
  return mbsalign_with_padding(src, dest, dest_size, width, align, flags, ' ');
618
0
}
619
620
/* Align a string, SRC, in a field of *WIDTH columns, handling multi-byte
621
   characters; write the result into the DEST_SIZE-byte buffer, DEST.
622
   ALIGNMENT specifies whether to left- or right-justify or to center.
623
   If SRC requires more than *WIDTH columns, truncate it to fit.
624
   When centering, the number of trailing spaces may be one less than the
625
   number of leading spaces. The FLAGS parameter is unused at present.
626
   Return the length in bytes required for the final result, not counting
627
   the trailing NUL.  A return value of DEST_SIZE or larger means there
628
   wasn't enough space.  DEST will be NUL terminated in any case.
629
   Return (size_t) -1 upon error (invalid multi-byte sequence in SRC,
630
   or malloc failure), unless MBA_UNIBYTE_FALLBACK is specified.
631
   Update *WIDTH to indicate how many columns were used before padding.  */
632
633
size_t
634
mbsalign_with_padding (const char *src, char *dest, size_t dest_size,
635
                 size_t *width, mbs_align_t align,
636
#ifdef HAVE_WIDECHAR
637
           int flags,
638
#else
639
           int flags __attribute__((__unused__)),
640
#endif
641
           int padchar)
642
0
{
643
0
  size_t ret = -1;
644
0
  size_t src_size = strlen (src) + 1;
645
0
  char *newstr = NULL;
646
0
  wchar_t *str_wc = NULL;
647
0
  const char *str_to_print = src;
648
0
  size_t n_cols = src_size - 1;
649
0
  size_t n_used_bytes = n_cols; /* Not including NUL */
650
0
  size_t n_spaces = 0, space_left;
651
652
0
#ifdef HAVE_WIDECHAR
653
0
  bool conversion = false;
654
0
  bool wc_enabled = false;
655
656
  /* In multi-byte locales convert to wide characters
657
     to allow easy truncation. Also determine number
658
     of screen columns used.  */
659
0
  if (MB_CUR_MAX > 1)
660
0
    {
661
0
      size_t src_chars = mbstowcs (NULL, src, 0);
662
0
      if (src_chars == (size_t) -1)
663
0
        {
664
0
          if (flags & MBA_UNIBYTE_FALLBACK)
665
0
            goto mbsalign_unibyte;
666
0
          else
667
0
            goto mbsalign_cleanup;
668
0
        }
669
0
      src_chars += 1; /* make space for NUL */
670
0
      str_wc = malloc (src_chars * sizeof (wchar_t));
671
0
      if (str_wc == NULL)
672
0
        {
673
0
          if (flags & MBA_UNIBYTE_FALLBACK)
674
0
            goto mbsalign_unibyte;
675
0
          else
676
0
            goto mbsalign_cleanup;
677
0
        }
678
0
      if (mbstowcs (str_wc, src, src_chars) != 0)
679
0
        {
680
0
          str_wc[src_chars - 1] = L'\0';
681
0
          wc_enabled = true;
682
0
          conversion = wc_ensure_printable (str_wc);
683
0
          n_cols = rpl_wcswidth (str_wc, src_chars);
684
0
        }
685
0
    }
686
687
  /* If we transformed or need to truncate the source string
688
     then create a modified copy of it.  */
689
0
  if (wc_enabled && (conversion || (n_cols > *width)))
690
0
    {
691
0
        if (conversion)
692
0
          {
693
             /* May have increased the size by converting
694
                \t to \uFFFD for example.  */
695
0
            src_size = wcstombs(NULL, str_wc, 0) + 1;
696
0
          }
697
0
        newstr = malloc (src_size);
698
0
        if (newstr == NULL)
699
0
        {
700
0
          if (flags & MBA_UNIBYTE_FALLBACK)
701
0
            goto mbsalign_unibyte;
702
0
          else
703
0
            goto mbsalign_cleanup;
704
0
        }
705
0
        str_to_print = newstr;
706
0
        n_cols = wc_truncate (str_wc, *width);
707
0
        n_used_bytes = wcstombs (newstr, str_wc, src_size);
708
0
    }
709
710
0
mbsalign_unibyte:
711
0
#endif
712
713
0
  if (n_cols > *width) /* Unibyte truncation required.  */
714
0
    {
715
0
      n_cols = *width;
716
0
      n_used_bytes = n_cols;
717
0
    }
718
719
0
  if (*width > n_cols) /* Padding required.  */
720
0
    n_spaces = *width - n_cols;
721
722
  /* indicate to caller how many cells needed (not including padding).  */
723
0
  *width = n_cols;
724
725
  /* indicate to caller how many bytes needed (not including NUL).  */
726
0
  ret = n_used_bytes + (n_spaces * 1);
727
728
  /* Write as much NUL terminated output to DEST as possible.  */
729
0
  if (dest_size != 0)
730
0
    {
731
0
      char *dest_end = dest + dest_size - 1;
732
0
      size_t start_spaces;
733
0
      size_t end_spaces;
734
735
0
      switch (align)
736
0
        {
737
0
        case MBS_ALIGN_CENTER:
738
0
          start_spaces = n_spaces / 2 + n_spaces % 2;
739
0
          end_spaces = n_spaces / 2;
740
0
          break;
741
0
        case MBS_ALIGN_LEFT:
742
0
          start_spaces = 0;
743
0
          end_spaces = n_spaces;
744
0
          break;
745
0
        case MBS_ALIGN_RIGHT:
746
0
          start_spaces = n_spaces;
747
0
          end_spaces = 0;
748
0
          break;
749
0
  default:
750
0
    abort();
751
0
        }
752
753
0
      dest = mbs_align_pad (dest, dest_end, start_spaces, padchar);
754
0
      space_left = dest_end - dest;
755
0
      dest = mempcpy (dest, str_to_print, min (n_used_bytes, space_left));
756
0
      mbs_align_pad (dest, dest_end, end_spaces, padchar);
757
0
    }
758
0
#ifdef HAVE_WIDECHAR
759
0
mbsalign_cleanup:
760
0
#endif
761
0
  free (str_wc);
762
0
  free (newstr);
763
764
0
  return ret;
765
0
}