1
2
3
4
5 package main
6
7 import (
8 "bufio"
9 "bytes"
10 "fmt"
11 "go/format"
12 "io"
13 "io/ioutil"
14 "log"
15 "net/http"
16 "strings"
17 )
18
19 type rrange struct {
20 lo rune
21 hi rune
22 }
23
24 func generate(out io.Writer, v string, arr []rrange) {
25 fmt.Fprintf(out, "var %s = table{\n\t", v)
26 for i := 0; i < len(arr); i++ {
27 fmt.Fprintf(out, "{0x%04X, 0x%04X},", arr[i].lo, arr[i].hi)
28 if i < len(arr)-1 {
29 if i%3 == 2 {
30 fmt.Fprint(out, "\n\t")
31 } else {
32 fmt.Fprint(out, " ")
33 }
34 }
35 }
36 fmt.Fprintln(out, "\n}")
37 }
38
39 func shapeup(p *[]rrange) {
40 arr := *p
41 for i := 0; i < len(arr)-1; i++ {
42 if arr[i].hi+1 == arr[i+1].lo {
43 lo := arr[i].lo
44 arr = append(arr[:i], arr[i+1:]...)
45 arr[i].lo = lo
46 i--
47 }
48 }
49 *p = arr
50 }
51
52 func eastasian(out io.Writer, in io.Reader) error {
53 scanner := bufio.NewScanner(in)
54
55 dbl := []rrange{}
56 amb := []rrange{}
57 cmb := []rrange{}
58 na := []rrange{}
59 nu := []rrange{}
60 for scanner.Scan() {
61 line := scanner.Text()
62 if strings.HasPrefix(line, "#") {
63 continue
64 }
65 var r1, r2 rune
66 var ss string
67 n, err := fmt.Sscanf(line, "%x..%x;%s ", &r1, &r2, &ss)
68 if err != nil || n == 2 {
69 n, err = fmt.Sscanf(line, "%x;%s ", &r1, &ss)
70 if err != nil || n != 2 {
71 continue
72 }
73 r2 = r1
74 }
75
76 if strings.Index(line, "COMBINING") != -1 {
77 cmb = append(cmb, rrange{
78 lo: r1,
79 hi: r2,
80 })
81 }
82
83 switch ss {
84 case "W", "F":
85 dbl = append(dbl, rrange{
86 lo: r1,
87 hi: r2,
88 })
89 case "A":
90 amb = append(amb, rrange{
91 lo: r1,
92 hi: r2,
93 })
94 case "Na":
95 na = append(na, rrange{
96 lo: r1,
97 hi: r2,
98 })
99 case "N":
100 nu = append(nu, rrange{
101 lo: r1,
102 hi: r2,
103 })
104 }
105 }
106
107 shapeup(&cmb)
108 generate(out, "combining", cmb)
109 fmt.Fprintln(out)
110
111 shapeup(&dbl)
112 generate(out, "doublewidth", dbl)
113 fmt.Fprintln(out)
114
115 shapeup(&amb)
116 generate(out, "ambiguous", amb)
117 fmt.Fprint(out)
118
119 shapeup(&na)
120 generate(out, "narrow", na)
121 fmt.Fprintln(out)
122
123 shapeup(&nu)
124 generate(out, "neutral", nu)
125 fmt.Fprintln(out)
126
127 return nil
128 }
129
130 func emoji(out io.Writer, in io.Reader) error {
131 scanner := bufio.NewScanner(in)
132
133 for scanner.Scan() {
134 line := scanner.Text()
135 if strings.Index(line, "Extended_Pictographic ; No") != -1 {
136 break
137 }
138 }
139
140 if scanner.Err() != nil {
141 return scanner.Err()
142 }
143
144 arr := []rrange{}
145 for scanner.Scan() {
146 line := scanner.Text()
147 if strings.HasPrefix(line, "#") {
148 continue
149 }
150 var r1, r2 rune
151 n, err := fmt.Sscanf(line, "%x..%x ", &r1, &r2)
152 if err != nil || n == 1 {
153 n, err = fmt.Sscanf(line, "%x ", &r1)
154 if err != nil || n != 1 {
155 continue
156 }
157 r2 = r1
158 }
159 if r2 < 0xFF {
160 continue
161 }
162
163 arr = append(arr, rrange{
164 lo: r1,
165 hi: r2,
166 })
167 }
168
169 shapeup(&arr)
170 generate(out, "emoji", arr)
171
172 return nil
173 }
174
175 func main() {
176 var buf bytes.Buffer
177 f := &buf
178 fmt.Fprint(f, "// Code generated by script/generate.go. DO NOT EDIT.\n\n")
179
180 fmt.Fprint(f, "package runewidth\n\n")
181
182 resp, err := http.Get("https://unicode.org/Public/13.0.0/ucd/EastAsianWidth.txt")
183 if err != nil {
184 log.Fatal(err)
185 }
186 defer resp.Body.Close()
187
188 eastasian(f, resp.Body)
189
190 resp, err = http.Get("https://unicode.org/Public/13.0.0/ucd/emoji/emoji-data.txt")
191 if err != nil {
192 log.Fatal(err)
193 }
194 defer resp.Body.Close()
195
196 emoji(f, resp.Body)
197
198 out, err := format.Source(f.Bytes())
199 if err != nil {
200 log.Fatal(err)
201 }
202 err = ioutil.WriteFile("runewidth_table.go", out, 0666)
203 if err != nil {
204 log.Fatal(err)
205 }
206 }
207
View as plain text