gui: Allow File|Open to select an encoding for system files.
[pspp] / src / libpspp / u8-line.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2011, 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 #include <config.h>
18
19 #include "libpspp/u8-line.h"
20 #include <unistr.h>
21 #include <uniwidth.h>
22 #include "libpspp/cast.h"
23 #include "libpspp/str.h"
24
25 /* Initializes LINE as an empty u8_line. */
26 void
27 u8_line_init (struct u8_line *line)
28 {
29   ds_init_empty (&line->s);
30   line->width = 0;
31 }
32
33 /* Frees data owned by LINE. */
34 void
35 u8_line_destroy (struct u8_line *line)
36 {
37   ds_destroy (&line->s);
38 }
39
40 /* Clears LINE to zero length. */
41 void
42 u8_line_clear (struct u8_line *line)
43 {
44   ds_clear (&line->s);
45   line->width = 0;
46 }
47
48 static int
49 u8_mb_to_display (int *wp, const uint8_t *s, size_t n)
50 {
51   size_t ofs;
52   ucs4_t uc;
53   int w;
54
55   ofs = u8_mbtouc (&uc, s, n);
56   if (ofs < n && s[ofs] == '\b')
57     {
58       ofs++;
59       ofs += u8_mbtouc (&uc, s + ofs, n - ofs);
60     }
61
62   w = uc_width (uc, "UTF-8");
63   if (w <= 0)
64     {
65       *wp = 0;
66       return ofs;
67     }
68
69   while (ofs < n)
70     {
71       int mblen = u8_mbtouc (&uc, s + ofs, n - ofs);
72       if (uc_width (uc, "UTF-8") > 0)
73         break;
74       ofs += mblen;
75     }
76
77   *wp = w;
78   return ofs;
79 }
80
81 /* Position of a character within a u8_line. */
82 struct u8_pos
83   {
84     /* 0-based display columns.
85
86        For a single-width character, x1 == x0 + 1.
87        For a double-width character, x1 == x0 + 2. */
88     int x0;
89     int x1;
90
91     /* Byte offsets.
92
93        For an ordinary ASCII character, ofs1 == ofs0 + 1.
94        For Unicode code point 0x80 or higher, 2 <= ofs1 - ofs0 <= 4. */
95     size_t ofs0;
96     size_t ofs1;
97   };
98
99 static void
100 u8_line_find_pos (struct u8_line *line, int target_x, struct u8_pos *c)
101 {
102   const uint8_t *s = CHAR_CAST (const uint8_t *, ds_cstr (&line->s));
103   size_t length = ds_length (&line->s);
104   size_t ofs;
105   int mblen;
106   int x;
107
108   x = 0;
109   for (ofs = 0; ; ofs += mblen)
110     {
111       int w;
112
113       mblen = u8_mb_to_display (&w, s + ofs, length - ofs);
114       if (x + w > target_x)
115         {
116           c->x0 = x;
117           c->x1 = x + w;
118           c->ofs0 = ofs;
119           c->ofs1 = ofs + mblen;
120           return;
121         }
122       x += w;
123     }
124 }
125
126 /* Prepares LINE to write N bytes of characters that comprise X1-X0 column
127    widths starting at 0-based column X0.  Returns the first byte of the N for
128    the caller to fill in. */
129 char *
130 u8_line_reserve (struct u8_line *line, int x0, int x1, int n)
131 {
132   if (x0 >= line->width)
133     {
134       /* The common case: adding new characters at the end of a line. */
135       ds_put_byte_multiple (&line->s, ' ', x0 - line->width);
136       line->width = x1;
137       return ds_put_uninit (&line->s, n);
138     }
139   else if (x0 == x1)
140     return NULL;
141   else
142     {
143       /* An unusual case: overwriting characters in the middle of a line.  We
144          don't keep any kind of mapping from bytes to display positions, so we
145          have to iterate over the whole line starting from the beginning. */
146       struct u8_pos p0, p1;
147       char *s;
148
149       /* Find the positions of the first and last character.  We must find both
150          characters' positions before changing the line, because that would
151          prevent finding the other character's position. */
152       u8_line_find_pos (line, x0, &p0);
153       if (x1 < line->width)
154         u8_line_find_pos (line, x1, &p1);
155
156       /* If a double-width character occupies both x0 - 1 and x0, then replace
157          its first character width by '?'. */
158       s = ds_data (&line->s);
159       while (p0.x0 < x0)
160         {
161           s[p0.ofs0++] = '?';
162           p0.x0++;
163         }
164
165       if (x1 >= line->width)
166         {
167           ds_truncate (&line->s, p0.ofs0);
168           line->width = x1;
169           return ds_put_uninit (&line->s, n);
170         }
171
172       /* If a double-width character occupies both x1 - 1 and x1, then replace
173          its second character width by '?'. */
174       if (p1.x0 < x1)
175         {
176           do
177             {
178               s[--p1.ofs1] = '?';
179               p1.x0++;
180             }
181           while (p1.x0 < x1);
182           return ds_splice_uninit (&line->s, p0.ofs0, p1.ofs1 - p0.ofs0, n);
183         }
184
185       return ds_splice_uninit (&line->s, p0.ofs0, p1.ofs0 - p0.ofs0, n);
186     }
187 }
188
189 /* Writes the N bytes of characters that comprise X1-X0 column widths into LINE
190    starting at 0-based column X0. */
191 void
192 u8_line_put (struct u8_line *line, int x0, int x1, const char *s, int n)
193 {
194   memcpy (u8_line_reserve (line, x0, x1, n), s, n);
195 }
196
197 /* Changes the width of LINE to X column widths.  If X is longer than LINE's
198    previous width, LINE is extended by appending spaces.  If X is shorter than
199    LINE's previous width, LINE is shortened by removing trailing characters. */
200 void
201 u8_line_set_length (struct u8_line *line, int x)
202 {
203   if (x > line->width)
204     {
205       ds_put_byte_multiple (&line->s, ' ', x - line->width);
206       line->width = x;
207     }
208   else if (x < line->width)
209     {
210       struct u8_pos pos;
211
212       u8_line_find_pos (line, x, &pos);
213       ds_truncate (&line->s, pos.ofs0);
214       line->width = pos.x0;
215       if (x > line->width)
216         {
217           ds_put_byte_multiple (&line->s, '?', x - line->width);
218           line->width = x;
219         }
220     }
221 }