1/* Determine the number of screen columns needed for a string.
2   Copyright (C) 2000-2012 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>.  */
16
17/* Written by Bruno Haible <haible@clisp.cons.org>.  */
18
19#include <config.h>
20
21/* Specification.  */
22#include "mbswidth.h"
23
24/* Get MB_CUR_MAX.  */
25#include <stdlib.h>
26
27#include <string.h>
28
29/* Get isprint().  */
30#include <ctype.h>
31
32/* Get mbstate_t, mbrtowc(), mbsinit(), wcwidth().  */
33#include <wchar.h>
34
35/* Get iswcntrl().  */
36#include <wctype.h>
37
38/* Get INT_MAX.  */
39#include <limits.h>
40
41/* Returns the number of columns needed to represent the multibyte
42   character string pointed to by STRING.  If a non-printable character
43   occurs, and MBSW_REJECT_UNPRINTABLE is specified, -1 is returned.
44   With flags = MBSW_REJECT_INVALID | MBSW_REJECT_UNPRINTABLE, this is
45   the multibyte analogue of the wcswidth function.  */
46int
47mbswidth (const char *string, int flags)
48{
49  return mbsnwidth (string, strlen (string), flags);
50}
51
52/* Returns the number of columns needed to represent the multibyte
53   character string pointed to by STRING of length NBYTES.  If a
54   non-printable character occurs, and MBSW_REJECT_UNPRINTABLE is
55   specified, -1 is returned.  */
56int
57mbsnwidth (const char *string, size_t nbytes, int flags)
58{
59  const char *p = string;
60  const char *plimit = p + nbytes;
61  int width;
62
63  width = 0;
64  if (MB_CUR_MAX > 1)
65    {
66      while (p < plimit)
67        switch (*p)
68          {
69            case ' ': case '!': case '"': case '#': case '%':
70            case '&': case '\'': case '(': case ')': case '*':
71            case '+': case ',': case '-': case '.': case '/':
72            case '0': case '1': case '2': case '3': case '4':
73            case '5': case '6': case '7': case '8': case '9':
74            case ':': case ';': case '<': case '=': case '>':
75            case '?':
76            case 'A': case 'B': case 'C': case 'D': case 'E':
77            case 'F': case 'G': case 'H': case 'I': case 'J':
78            case 'K': case 'L': case 'M': case 'N': case 'O':
79            case 'P': case 'Q': case 'R': case 'S': case 'T':
80            case 'U': case 'V': case 'W': case 'X': case 'Y':
81            case 'Z':
82            case '[': case '\\': case ']': case '^': case '_':
83            case 'a': case 'b': case 'c': case 'd': case 'e':
84            case 'f': case 'g': case 'h': case 'i': case 'j':
85            case 'k': case 'l': case 'm': case 'n': case 'o':
86            case 'p': case 'q': case 'r': case 's': case 't':
87            case 'u': case 'v': case 'w': case 'x': case 'y':
88            case 'z': case '{': case '|': case '}': case '~':
89              /* These characters are printable ASCII characters.  */
90              p++;
91              width++;
92              break;
93            default:
94              /* If we have a multibyte sequence, scan it up to its end.  */
95              {
96                mbstate_t mbstate;
97                memset (&mbstate, 0, sizeof mbstate);
98                do
99                  {
100                    wchar_t wc;
101                    size_t bytes;
102                    int w;
103
104                    bytes = mbrtowc (&wc, p, plimit - p, &mbstate);
105
106                    if (bytes == (size_t) -1)
107                      /* An invalid multibyte sequence was encountered.  */
108                      {
109                        if (!(flags & MBSW_REJECT_INVALID))
110                          {
111                            p++;
112                            width++;
113                            break;
114                          }
115                        else
116                          return -1;
117                      }
118
119                    if (bytes == (size_t) -2)
120                      /* An incomplete multibyte character at the end.  */
121                      {
122                        if (!(flags & MBSW_REJECT_INVALID))
123                          {
124                            p = plimit;
125                            width++;
126                            break;
127                          }
128                        else
129                          return -1;
130                      }
131
132                    if (bytes == 0)
133                      /* A null wide character was encountered.  */
134                      bytes = 1;
135
136                    w = wcwidth (wc);
137                    if (w >= 0)
138                      /* A printable multibyte character.  */
139                      {
140                        if (w > INT_MAX - width)
141                          goto overflow;
142                        width += w;
143                      }
144                    else
145                      /* An unprintable multibyte character.  */
146                      if (!(flags & MBSW_REJECT_UNPRINTABLE))
147                        {
148                          if (!iswcntrl (wc))
149                            {
150                              if (width == INT_MAX)
151                                goto overflow;
152                              width++;
153                            }
154                        }
155                      else
156                        return -1;
157
158                    p += bytes;
159                  }
160                while (! mbsinit (&mbstate));
161              }
162              break;
163          }
164      return width;
165    }
166
167  while (p < plimit)
168    {
169      unsigned char c = (unsigned char) *p++;
170
171      if (isprint (c))
172        {
173          if (width == INT_MAX)
174            goto overflow;
175          width++;
176        }
177      else if (!(flags & MBSW_REJECT_UNPRINTABLE))
178        {
179          if (!iscntrl (c))
180            {
181              if (width == INT_MAX)
182                goto overflow;
183              width++;
184            }
185        }
186      else
187        return -1;
188    }
189  return width;
190
191 overflow:
192  return INT_MAX;
193}
194