1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package norm
6
7import "unicode/utf8"
8
9const (
10	maxNonStarters = 30
11	// The maximum number of characters needed for a buffer is
12	// maxNonStarters + 1 for the starter + 1 for the GCJ
13	maxBufferSize    = maxNonStarters + 2
14	maxNFCExpansion  = 3  // NFC(0x1D160)
15	maxNFKCExpansion = 18 // NFKC(0xFDFA)
16
17	maxByteBufferSize = utf8.UTFMax * maxBufferSize // 128
18)
19
20// ssState is used for reporting the segment state after inserting a rune.
21// It is returned by streamSafe.next.
22type ssState int
23
24const (
25	// Indicates a rune was successfully added to the segment.
26	ssSuccess ssState = iota
27	// Indicates a rune starts a new segment and should not be added.
28	ssStarter
29	// Indicates a rune caused a segment overflow and a CGJ should be inserted.
30	ssOverflow
31)
32
33// streamSafe implements the policy of when a CGJ should be inserted.
34type streamSafe uint8
35
36// first inserts the first rune of a segment. It is a faster version of next if
37// it is known p represents the first rune in a segment.
38func (ss *streamSafe) first(p Properties) {
39	*ss = streamSafe(p.nTrailingNonStarters())
40}
41
42// insert returns a ssState value to indicate whether a rune represented by p
43// can be inserted.
44func (ss *streamSafe) next(p Properties) ssState {
45	if *ss > maxNonStarters {
46		panic("streamSafe was not reset")
47	}
48	n := p.nLeadingNonStarters()
49	if *ss += streamSafe(n); *ss > maxNonStarters {
50		*ss = 0
51		return ssOverflow
52	}
53	// The Stream-Safe Text Processing prescribes that the counting can stop
54	// as soon as a starter is encountered. However, there are some starters,
55	// like Jamo V and T, that can combine with other runes, leaving their
56	// successive non-starters appended to the previous, possibly causing an
57	// overflow. We will therefore consider any rune with a non-zero nLead to
58	// be a non-starter. Note that it always hold that if nLead > 0 then
59	// nLead == nTrail.
60	if n == 0 {
61		*ss = streamSafe(p.nTrailingNonStarters())
62		return ssStarter
63	}
64	return ssSuccess
65}
66
67// backwards is used for checking for overflow and segment starts
68// when traversing a string backwards. Users do not need to call first
69// for the first rune. The state of the streamSafe retains the count of
70// the non-starters loaded.
71func (ss *streamSafe) backwards(p Properties) ssState {
72	if *ss > maxNonStarters {
73		panic("streamSafe was not reset")
74	}
75	c := *ss + streamSafe(p.nTrailingNonStarters())
76	if c > maxNonStarters {
77		return ssOverflow
78	}
79	*ss = c
80	if p.nLeadingNonStarters() == 0 {
81		return ssStarter
82	}
83	return ssSuccess
84}
85
86func (ss streamSafe) isMax() bool {
87	return ss == maxNonStarters
88}
89
90// GraphemeJoiner is inserted after maxNonStarters non-starter runes.
91const GraphemeJoiner = "\u034F"
92
93// reorderBuffer is used to normalize a single segment.  Characters inserted with
94// insert are decomposed and reordered based on CCC. The compose method can
95// be used to recombine characters.  Note that the byte buffer does not hold
96// the UTF-8 characters in order.  Only the rune array is maintained in sorted
97// order. flush writes the resulting segment to a byte array.
98type reorderBuffer struct {
99	rune  [maxBufferSize]Properties // Per character info.
100	byte  [maxByteBufferSize]byte   // UTF-8 buffer. Referenced by runeInfo.pos.
101	nbyte uint8                     // Number or bytes.
102	ss    streamSafe                // For limiting length of non-starter sequence.
103	nrune int                       // Number of runeInfos.
104	f     formInfo
105
106	src      input
107	nsrc     int
108	tmpBytes input
109
110	out    []byte
111	flushF func(*reorderBuffer) bool
112}
113
114func (rb *reorderBuffer) init(f Form, src []byte) {
115	rb.f = *formTable[f]
116	rb.src.setBytes(src)
117	rb.nsrc = len(src)
118	rb.ss = 0
119}
120
121func (rb *reorderBuffer) initString(f Form, src string) {
122	rb.f = *formTable[f]
123	rb.src.setString(src)
124	rb.nsrc = len(src)
125	rb.ss = 0
126}
127
128func (rb *reorderBuffer) setFlusher(out []byte, f func(*reorderBuffer) bool) {
129	rb.out = out
130	rb.flushF = f
131}
132
133// reset discards all characters from the buffer.
134func (rb *reorderBuffer) reset() {
135	rb.nrune = 0
136	rb.nbyte = 0
137}
138
139func (rb *reorderBuffer) doFlush() bool {
140	if rb.f.composing {
141		rb.compose()
142	}
143	res := rb.flushF(rb)
144	rb.reset()
145	return res
146}
147
148// appendFlush appends the normalized segment to rb.out.
149func appendFlush(rb *reorderBuffer) bool {
150	for i := 0; i < rb.nrune; i++ {
151		start := rb.rune[i].pos
152		end := start + rb.rune[i].size
153		rb.out = append(rb.out, rb.byte[start:end]...)
154	}
155	return true
156}
157
158// flush appends the normalized segment to out and resets rb.
159func (rb *reorderBuffer) flush(out []byte) []byte {
160	for i := 0; i < rb.nrune; i++ {
161		start := rb.rune[i].pos
162		end := start + rb.rune[i].size
163		out = append(out, rb.byte[start:end]...)
164	}
165	rb.reset()
166	return out
167}
168
169// flushCopy copies the normalized segment to buf and resets rb.
170// It returns the number of bytes written to buf.
171func (rb *reorderBuffer) flushCopy(buf []byte) int {
172	p := 0
173	for i := 0; i < rb.nrune; i++ {
174		runep := rb.rune[i]
175		p += copy(buf[p:], rb.byte[runep.pos:runep.pos+runep.size])
176	}
177	rb.reset()
178	return p
179}
180
181// insertOrdered inserts a rune in the buffer, ordered by Canonical Combining Class.
182// It returns false if the buffer is not large enough to hold the rune.
183// It is used internally by insert and insertString only.
184func (rb *reorderBuffer) insertOrdered(info Properties) {
185	n := rb.nrune
186	b := rb.rune[:]
187	cc := info.ccc
188	if cc > 0 {
189		// Find insertion position + move elements to make room.
190		for ; n > 0; n-- {
191			if b[n-1].ccc <= cc {
192				break
193			}
194			b[n] = b[n-1]
195		}
196	}
197	rb.nrune += 1
198	pos := uint8(rb.nbyte)
199	rb.nbyte += utf8.UTFMax
200	info.pos = pos
201	b[n] = info
202}
203
204// insertErr is an error code returned by insert. Using this type instead
205// of error improves performance up to 20% for many of the benchmarks.
206type insertErr int
207
208const (
209	iSuccess insertErr = -iota
210	iShortDst
211	iShortSrc
212)
213
214// insertFlush inserts the given rune in the buffer ordered by CCC.
215// If a decomposition with multiple segments are encountered, they leading
216// ones are flushed.
217// It returns a non-zero error code if the rune was not inserted.
218func (rb *reorderBuffer) insertFlush(src input, i int, info Properties) insertErr {
219	if rune := src.hangul(i); rune != 0 {
220		rb.decomposeHangul(rune)
221		return iSuccess
222	}
223	if info.hasDecomposition() {
224		return rb.insertDecomposed(info.Decomposition())
225	}
226	rb.insertSingle(src, i, info)
227	return iSuccess
228}
229
230// insertUnsafe inserts the given rune in the buffer ordered by CCC.
231// It is assumed there is sufficient space to hold the runes. It is the
232// responsibility of the caller to ensure this. This can be done by checking
233// the state returned by the streamSafe type.
234func (rb *reorderBuffer) insertUnsafe(src input, i int, info Properties) {
235	if rune := src.hangul(i); rune != 0 {
236		rb.decomposeHangul(rune)
237	}
238	if info.hasDecomposition() {
239		// TODO: inline.
240		rb.insertDecomposed(info.Decomposition())
241	} else {
242		rb.insertSingle(src, i, info)
243	}
244}
245
246// insertDecomposed inserts an entry in to the reorderBuffer for each rune
247// in dcomp. dcomp must be a sequence of decomposed UTF-8-encoded runes.
248// It flushes the buffer on each new segment start.
249func (rb *reorderBuffer) insertDecomposed(dcomp []byte) insertErr {
250	rb.tmpBytes.setBytes(dcomp)
251	// As the streamSafe accounting already handles the counting for modifiers,
252	// we don't have to call next. However, we do need to keep the accounting
253	// intact when flushing the buffer.
254	for i := 0; i < len(dcomp); {
255		info := rb.f.info(rb.tmpBytes, i)
256		if info.BoundaryBefore() && rb.nrune > 0 && !rb.doFlush() {
257			return iShortDst
258		}
259		i += copy(rb.byte[rb.nbyte:], dcomp[i:i+int(info.size)])
260		rb.insertOrdered(info)
261	}
262	return iSuccess
263}
264
265// insertSingle inserts an entry in the reorderBuffer for the rune at
266// position i. info is the runeInfo for the rune at position i.
267func (rb *reorderBuffer) insertSingle(src input, i int, info Properties) {
268	src.copySlice(rb.byte[rb.nbyte:], i, i+int(info.size))
269	rb.insertOrdered(info)
270}
271
272// insertCGJ inserts a Combining Grapheme Joiner (0x034f) into rb.
273func (rb *reorderBuffer) insertCGJ() {
274	rb.insertSingle(input{str: GraphemeJoiner}, 0, Properties{size: uint8(len(GraphemeJoiner))})
275}
276
277// appendRune inserts a rune at the end of the buffer. It is used for Hangul.
278func (rb *reorderBuffer) appendRune(r rune) {
279	bn := rb.nbyte
280	sz := utf8.EncodeRune(rb.byte[bn:], rune(r))
281	rb.nbyte += utf8.UTFMax
282	rb.rune[rb.nrune] = Properties{pos: bn, size: uint8(sz)}
283	rb.nrune++
284}
285
286// assignRune sets a rune at position pos. It is used for Hangul and recomposition.
287func (rb *reorderBuffer) assignRune(pos int, r rune) {
288	bn := rb.rune[pos].pos
289	sz := utf8.EncodeRune(rb.byte[bn:], rune(r))
290	rb.rune[pos] = Properties{pos: bn, size: uint8(sz)}
291}
292
293// runeAt returns the rune at position n. It is used for Hangul and recomposition.
294func (rb *reorderBuffer) runeAt(n int) rune {
295	inf := rb.rune[n]
296	r, _ := utf8.DecodeRune(rb.byte[inf.pos : inf.pos+inf.size])
297	return r
298}
299
300// bytesAt returns the UTF-8 encoding of the rune at position n.
301// It is used for Hangul and recomposition.
302func (rb *reorderBuffer) bytesAt(n int) []byte {
303	inf := rb.rune[n]
304	return rb.byte[inf.pos : int(inf.pos)+int(inf.size)]
305}
306
307// For Hangul we combine algorithmically, instead of using tables.
308const (
309	hangulBase  = 0xAC00 // UTF-8(hangulBase) -> EA B0 80
310	hangulBase0 = 0xEA
311	hangulBase1 = 0xB0
312	hangulBase2 = 0x80
313
314	hangulEnd  = hangulBase + jamoLVTCount // UTF-8(0xD7A4) -> ED 9E A4
315	hangulEnd0 = 0xED
316	hangulEnd1 = 0x9E
317	hangulEnd2 = 0xA4
318
319	jamoLBase  = 0x1100 // UTF-8(jamoLBase) -> E1 84 00
320	jamoLBase0 = 0xE1
321	jamoLBase1 = 0x84
322	jamoLEnd   = 0x1113
323	jamoVBase  = 0x1161
324	jamoVEnd   = 0x1176
325	jamoTBase  = 0x11A7
326	jamoTEnd   = 0x11C3
327
328	jamoTCount   = 28
329	jamoVCount   = 21
330	jamoVTCount  = 21 * 28
331	jamoLVTCount = 19 * 21 * 28
332)
333
334const hangulUTF8Size = 3
335
336func isHangul(b []byte) bool {
337	if len(b) < hangulUTF8Size {
338		return false
339	}
340	b0 := b[0]
341	if b0 < hangulBase0 {
342		return false
343	}
344	b1 := b[1]
345	switch {
346	case b0 == hangulBase0:
347		return b1 >= hangulBase1
348	case b0 < hangulEnd0:
349		return true
350	case b0 > hangulEnd0:
351		return false
352	case b1 < hangulEnd1:
353		return true
354	}
355	return b1 == hangulEnd1 && b[2] < hangulEnd2
356}
357
358func isHangulString(b string) bool {
359	if len(b) < hangulUTF8Size {
360		return false
361	}
362	b0 := b[0]
363	if b0 < hangulBase0 {
364		return false
365	}
366	b1 := b[1]
367	switch {
368	case b0 == hangulBase0:
369		return b1 >= hangulBase1
370	case b0 < hangulEnd0:
371		return true
372	case b0 > hangulEnd0:
373		return false
374	case b1 < hangulEnd1:
375		return true
376	}
377	return b1 == hangulEnd1 && b[2] < hangulEnd2
378}
379
380// Caller must ensure len(b) >= 2.
381func isJamoVT(b []byte) bool {
382	// True if (rune & 0xff00) == jamoLBase
383	return b[0] == jamoLBase0 && (b[1]&0xFC) == jamoLBase1
384}
385
386func isHangulWithoutJamoT(b []byte) bool {
387	c, _ := utf8.DecodeRune(b)
388	c -= hangulBase
389	return c < jamoLVTCount && c%jamoTCount == 0
390}
391
392// decomposeHangul writes the decomposed Hangul to buf and returns the number
393// of bytes written.  len(buf) should be at least 9.
394func decomposeHangul(buf []byte, r rune) int {
395	const JamoUTF8Len = 3
396	r -= hangulBase
397	x := r % jamoTCount
398	r /= jamoTCount
399	utf8.EncodeRune(buf, jamoLBase+r/jamoVCount)
400	utf8.EncodeRune(buf[JamoUTF8Len:], jamoVBase+r%jamoVCount)
401	if x != 0 {
402		utf8.EncodeRune(buf[2*JamoUTF8Len:], jamoTBase+x)
403		return 3 * JamoUTF8Len
404	}
405	return 2 * JamoUTF8Len
406}
407
408// decomposeHangul algorithmically decomposes a Hangul rune into
409// its Jamo components.
410// See https://unicode.org/reports/tr15/#Hangul for details on decomposing Hangul.
411func (rb *reorderBuffer) decomposeHangul(r rune) {
412	r -= hangulBase
413	x := r % jamoTCount
414	r /= jamoTCount
415	rb.appendRune(jamoLBase + r/jamoVCount)
416	rb.appendRune(jamoVBase + r%jamoVCount)
417	if x != 0 {
418		rb.appendRune(jamoTBase + x)
419	}
420}
421
422// combineHangul algorithmically combines Jamo character components into Hangul.
423// See https://unicode.org/reports/tr15/#Hangul for details on combining Hangul.
424func (rb *reorderBuffer) combineHangul(s, i, k int) {
425	b := rb.rune[:]
426	bn := rb.nrune
427	for ; i < bn; i++ {
428		cccB := b[k-1].ccc
429		cccC := b[i].ccc
430		if cccB == 0 {
431			s = k - 1
432		}
433		if s != k-1 && cccB >= cccC {
434			// b[i] is blocked by greater-equal cccX below it
435			b[k] = b[i]
436			k++
437		} else {
438			l := rb.runeAt(s) // also used to compare to hangulBase
439			v := rb.runeAt(i) // also used to compare to jamoT
440			switch {
441			case jamoLBase <= l && l < jamoLEnd &&
442				jamoVBase <= v && v < jamoVEnd:
443				// 11xx plus 116x to LV
444				rb.assignRune(s, hangulBase+
445					(l-jamoLBase)*jamoVTCount+(v-jamoVBase)*jamoTCount)
446			case hangulBase <= l && l < hangulEnd &&
447				jamoTBase < v && v < jamoTEnd &&
448				((l-hangulBase)%jamoTCount) == 0:
449				// ACxx plus 11Ax to LVT
450				rb.assignRune(s, l+v-jamoTBase)
451			default:
452				b[k] = b[i]
453				k++
454			}
455		}
456	}
457	rb.nrune = k
458}
459
460// compose recombines the runes in the buffer.
461// It should only be used to recompose a single segment, as it will not
462// handle alternations between Hangul and non-Hangul characters correctly.
463func (rb *reorderBuffer) compose() {
464	// Lazily load the map used by the combine func below, but do
465	// it outside of the loop.
466	recompMapOnce.Do(buildRecompMap)
467
468	// UAX #15, section X5 , including Corrigendum #5
469	// "In any character sequence beginning with starter S, a character C is
470	//  blocked from S if and only if there is some character B between S
471	//  and C, and either B is a starter or it has the same or higher
472	//  combining class as C."
473	bn := rb.nrune
474	if bn == 0 {
475		return
476	}
477	k := 1
478	b := rb.rune[:]
479	for s, i := 0, 1; i < bn; i++ {
480		if isJamoVT(rb.bytesAt(i)) {
481			// Redo from start in Hangul mode. Necessary to support
482			// U+320E..U+321E in NFKC mode.
483			rb.combineHangul(s, i, k)
484			return
485		}
486		ii := b[i]
487		// We can only use combineForward as a filter if we later
488		// get the info for the combined character. This is more
489		// expensive than using the filter. Using combinesBackward()
490		// is safe.
491		if ii.combinesBackward() {
492			cccB := b[k-1].ccc
493			cccC := ii.ccc
494			blocked := false // b[i] blocked by starter or greater or equal CCC?
495			if cccB == 0 {
496				s = k - 1
497			} else {
498				blocked = s != k-1 && cccB >= cccC
499			}
500			if !blocked {
501				combined := combine(rb.runeAt(s), rb.runeAt(i))
502				if combined != 0 {
503					rb.assignRune(s, combined)
504					continue
505				}
506			}
507		}
508		b[k] = b[i]
509		k++
510	}
511	rb.nrune = k
512}
513