work on docs
[pspp] / src / libpspp / u8-line.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2011, 2012, 2020 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 bool
100 u8_line_find_pos (const 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   c->x0 = 0;
110   c->ofs0 = 0;
111   for (ofs = 0; ofs <= length; ofs += mblen)
112     {
113       int w;
114       c->x0 = x;
115       c->ofs0 = ofs;
116       mblen = u8_mb_to_display (&w, s + ofs, length - ofs);
117       if (x + w > target_x)
118         {
119           c->x1 = x + w;
120           c->ofs1 = ofs + mblen;
121           return true;
122         }
123       x += w;
124     }
125
126   /* This can happen if there are non-printable characters
127      in the line.  */
128   c->x1 = x;
129   c->ofs1 = ofs;
130   return false;
131 }
132
133 /* Prepares LINE to write N bytes of characters that comprise X1-X0 column
134    widths starting at 0-based column X0.  Returns the first byte of the N for
135    the caller to fill in. */
136 char *
137 u8_line_reserve (struct u8_line *line, int x0, int x1, int n)
138 {
139   assert (x1 >= x0);
140   if (x0 >= line->width)
141     {
142       /* The common case: adding new characters at the end of a line. */
143       ds_put_byte_multiple (&line->s, ' ', x0 - line->width);
144       line->width = x1;
145       return ds_put_uninit (&line->s, n);
146     }
147   else if (x0 == x1)
148     return NULL;
149   else
150     {
151       /* An unusual case: overwriting characters in the middle of a line.  We
152          don't keep any kind of mapping from bytes to display positions, so we
153          have to iterate over the whole line starting from the beginning. */
154       struct u8_pos p0, p1;
155       char *s;
156
157       /* Find the positions of the first and last character.  We must find both
158          characters' positions before changing the line, because that would
159          prevent finding the other character's position. */
160       u8_line_find_pos (line, x0, &p0);
161       if (x1 < line->width)
162         u8_line_find_pos (line, x1, &p1);
163
164       /* If a double-width character occupies both x0 - 1 and x0, then replace
165          its first character width by '?'. */
166       s = ds_data (&line->s);
167       while (p0.x0 < x0)
168         {
169           s[p0.ofs0++] = '?';
170           p0.x0++;
171         }
172
173       if (x1 >= line->width)
174         {
175           ds_truncate (&line->s, p0.ofs0);
176           line->width = x1;
177           return ds_put_uninit (&line->s, n);
178         }
179
180       /* If a double-width character occupies both x1 - 1 and x1, then replace
181          its second character width by '?'. */
182       if (p1.x0 < x1)
183         {
184           do
185             {
186               s[--p1.ofs1] = '?';
187               p1.x0++;
188             }
189           while (p1.x0 < x1);
190           assert (p1.ofs1 >= p0.ofs0);
191           return ds_splice_uninit (&line->s, p0.ofs0, p1.ofs1 - p0.ofs0, n);
192         }
193
194       assert (p1.ofs0 >= p0.ofs0);
195       return ds_splice_uninit (&line->s, p0.ofs0, p1.ofs0 - p0.ofs0, n);
196     }
197 }
198
199 /* Writes the N bytes of characters that comprise X1-X0 column widths into LINE
200    starting at 0-based column X0. */
201 void
202 u8_line_put (struct u8_line *line, int x0, int x1, const char *s, int n)
203 {
204   memcpy (u8_line_reserve (line, x0, x1, n), s, n);
205 }
206
207 /* Changes the width of LINE to X column widths.  If X is longer than LINE's
208    previous width, LINE is extended by appending spaces.  If X is shorter than
209    LINE's previous width, LINE is shortened by removing trailing characters. */
210 void
211 u8_line_set_length (struct u8_line *line, int x)
212 {
213   if (x > line->width)
214     {
215       ds_put_byte_multiple (&line->s, ' ', x - line->width);
216       line->width = x;
217     }
218   else if (x < line->width)
219     {
220       struct u8_pos pos;
221
222       u8_line_find_pos (line, x, &pos);
223       ds_truncate (&line->s, pos.ofs0);
224       line->width = pos.x0;
225       if (x > line->width)
226         {
227           ds_put_byte_multiple (&line->s, '?', x - line->width);
228           line->width = x;
229         }
230     }
231 }