work on print encodigns
[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 void
26 u8_line_init (struct u8_line *line)
27 {
28   ds_init_empty (&line->s);
29   line->width = 0;
30 }
31
32 void
33 u8_line_clear (struct u8_line *line)
34 {
35   ds_clear (&line->s);
36   line->width = 0;
37 }
38
39 void
40 u8_line_destroy (struct u8_line *line)
41 {
42   ds_destroy (&line->s);
43 }
44
45 static int
46 u8_mb_to_display (int *wp, const uint8_t *s, size_t n)
47 {
48   size_t ofs;
49   ucs4_t uc;
50   int w;
51
52   ofs = u8_mbtouc (&uc, s, n);
53   if (ofs < n && s[ofs] == '\b')
54     {
55       ofs++;
56       ofs += u8_mbtouc (&uc, s + ofs, n - ofs);
57     }
58
59   w = uc_width (uc, "UTF-8");
60   if (w <= 0)
61     {
62       *wp = 0;
63       return ofs;
64     }
65
66   while (ofs < n)
67     {
68       int mblen = u8_mbtouc (&uc, s + ofs, n - ofs);
69       if (uc_width (uc, "UTF-8") > 0)
70         break;
71       ofs += mblen;
72     }
73
74   *wp = w;
75   return ofs;
76 }
77
78 struct u8_pos
79   {
80     int x0;
81     int x1;
82     size_t ofs0;
83     size_t ofs1;
84   };
85
86 static void
87 u8_line_find_pos (struct u8_line *line, int target_x, struct u8_pos *c)
88 {
89   const uint8_t *s = CHAR_CAST (const uint8_t *, ds_cstr (&line->s));
90   size_t length = ds_length (&line->s);
91   size_t ofs;
92   int mblen;
93   int x;
94
95   x = 0;
96   for (ofs = 0; ; ofs += mblen)
97     {
98       int w;
99
100       mblen = u8_mb_to_display (&w, s + ofs, length - ofs);
101       if (x + w > target_x)
102         {
103           c->x0 = x;
104           c->x1 = x + w;
105           c->ofs0 = ofs;
106           c->ofs1 = ofs + mblen;
107           return;
108         }
109       x += w;
110     }
111 }
112
113 char *
114 u8_line_reserve (struct u8_line *line, int x0, int x1, int n)
115 {
116   if (x0 >= line->width)
117     {
118       /* The common case: adding new characters at the end of a line. */
119       ds_put_byte_multiple (&line->s, ' ', x0 - line->width);
120       line->width = x1;
121       return ds_put_uninit (&line->s, n);
122     }
123   else if (x0 == x1)
124     return NULL;
125   else
126     {
127       /* An unusual case: overwriting characters in the middle of a line.  We
128          don't keep any kind of mapping from bytes to display positions, so we
129          have to iterate over the whole line starting from the beginning. */
130       struct u8_pos p0, p1;
131       char *s;
132
133       /* Find the positions of the first and last character.  We must find both
134          characters' positions before changing the line, because that would
135          prevent finding the other character's position. */
136       u8_line_find_pos (line, x0, &p0);
137       if (x1 < line->width)
138         u8_line_find_pos (line, x1, &p1);
139
140       /* If a double-width character occupies both x0 - 1 and x0, then replace
141          its first character width by '?'. */
142       s = ds_data (&line->s);
143       while (p0.x0 < x0)
144         {
145           s[p0.ofs0++] = '?';
146           p0.x0++;
147         }
148
149       if (x1 >= line->width)
150         {
151           ds_truncate (&line->s, p0.ofs0);
152           line->width = x1;
153           return ds_put_uninit (&line->s, n);
154         }
155
156       /* If a double-width character occupies both x1 - 1 and x1, then replace
157          its second character width by '?'. */
158       if (p1.x0 < x1)
159         {
160           do
161             {
162               s[--p1.ofs1] = '?';
163               p1.x0++;
164             }
165           while (p1.x0 < x1);
166           return ds_splice_uninit (&line->s, p0.ofs0, p1.ofs1 - p0.ofs0, n);
167         }
168
169       return ds_splice_uninit (&line->s, p0.ofs0, p1.ofs0 - p0.ofs0, n);
170     }
171 }