1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
import std/strutils
import std/unicode
import utils/map
const CanHaveDakuten = ("かきくけこさしすせそたちつてとはひふへほカキクケコ" &
"サシスセソタチツテトハヒフヘホ").toRunes()
const CanHaveHanDakuten = "はひふへほハヒフヘホ".toRunes()
const HasDakuten = ("がぎぐげござじずぜぞだぢづでどばびぶべぼガギグゲゴ" &
"ザジズゼゾダヂヅデドバビブベボ").toRunes()
const HasHanDakuten = "ぱぴぷぺぽパピプペポ".toRunes()
# Halfwidth to fullwidth & vice versa
const halfFullMap = (func(): seq[tuple[half, full1, full2: Rune]] =
result = @[]
const map = staticRead"res/widthconvmap.tab"
for line in map.split('\n'):
if line == "":
break
var i = 0
var half: Rune
fastRuneAt(line, i, half)
assert line[i] == '\t'
inc i
var full1: Rune
fastRuneAt(line, i, full1)
var full2 = Rune(0)
if i < line.len:
assert line[i] == '\t'
inc i
fastRuneAt(line, i, full2)
result.add((half, full1, full2))
)()
func halfwidth(r: Rune): Rune =
if r != Rune(0): # special case to avoid comparison with f2
for (h, f1, f2) in halfFullMap:
if f1 == r or f2 == r:
return h
return r
const HalfDakuten = Rune(0xFF9E) # half-width dakuten
const HalfHanDakuten = Rune(0xFF9F) # half-width handakuten
# Note: in unicode, char + 1 is dakuten and char + 2 handakuten
func halfwidth*(s: string): string =
result = ""
for r in s.runes:
case r
of HasDakuten:
result &= halfwidth(Rune(uint32(r) - 1))
result &= HalfDakuten
of HasHanDakuten:
result &= halfwidth(Rune(uint32(r) - 2))
result &= HalfHanDakuten
else:
result &= halfwidth(r)
func fullwidth(r: Rune): Rune =
if r != Rune(0): # special case to avoid comparison with f2
for (h, f1, f2) in halfFullMap:
if h == r:
return f1
return r
func fullwidth*(s: string): string =
result = ""
var lastr = Rune(0)
for r in s.runes:
if lastr != Rune(0):
if r == HalfDakuten:
# flush with dakuten
result &= Rune(uint32(lastr) + 1)
lastr = Rune(0)
continue
elif r == HalfHanDakuten and lastr in CanHaveHanDakuten:
# flush with handakuten
result &= Rune(uint32(lastr) + 2)
lastr = Rune(0)
continue
result &= lastr
lastr = Rune(0)
let r = fullwidth(r)
if r in CanHaveDakuten:
lastr = r
else:
result &= r
if lastr != Rune(0):
# flush
result &= lastr
const kanamap = staticRead"res/kanamap.tab"
func genFullSizeMap(): seq[(uint32, uint32)] =
result = @[]
for line in kanamap.split('\n'):
if line.len == 0: break
let rs = line.toRunes()
assert rs[1] == Rune('\t')
result.add((uint32(rs[0]), uint32(rs[2])))
const fullSizeMap = genFullSizeMap()
proc fullsize*(s: string): string =
result = ""
for r in s.runes:
let i = searchInMap(fullSizeMap, uint32(r))
if i == -1:
result &= r
else:
result &= $Rune(fullSizeMap[i][1])
|