Elia Battiston | be9edc1 | 2022-03-09 11:35:58 +0100 | [diff] [blame] | 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 | |
| 5 | package norm |
| 6 | |
| 7 | import "encoding/binary" |
| 8 | |
| 9 | // This file contains Form-specific logic and wrappers for data in tables.go. |
| 10 | |
| 11 | // Rune info is stored in a separate trie per composing form. A composing form |
| 12 | // and its corresponding decomposing form share the same trie. Each trie maps |
| 13 | // a rune to a uint16. The values take two forms. For v >= 0x8000: |
| 14 | // bits |
| 15 | // 15: 1 (inverse of NFD_QC bit of qcInfo) |
| 16 | // 13..7: qcInfo (see below). isYesD is always true (no decompostion). |
| 17 | // 6..0: ccc (compressed CCC value). |
| 18 | // For v < 0x8000, the respective rune has a decomposition and v is an index |
| 19 | // into a byte array of UTF-8 decomposition sequences and additional info and |
| 20 | // has the form: |
| 21 | // <header> <decomp_byte>* [<tccc> [<lccc>]] |
| 22 | // The header contains the number of bytes in the decomposition (excluding this |
| 23 | // length byte). The two most significant bits of this length byte correspond |
| 24 | // to bit 5 and 4 of qcInfo (see below). The byte sequence itself starts at v+1. |
| 25 | // The byte sequence is followed by a trailing and leading CCC if the values |
| 26 | // for these are not zero. The value of v determines which ccc are appended |
| 27 | // to the sequences. For v < firstCCC, there are none, for v >= firstCCC, |
| 28 | // the sequence is followed by a trailing ccc, and for v >= firstLeadingCC |
| 29 | // there is an additional leading ccc. The value of tccc itself is the |
| 30 | // trailing CCC shifted left 2 bits. The two least-significant bits of tccc |
| 31 | // are the number of trailing non-starters. |
| 32 | |
| 33 | const ( |
| 34 | qcInfoMask = 0x3F // to clear all but the relevant bits in a qcInfo |
| 35 | headerLenMask = 0x3F // extract the length value from the header byte |
| 36 | headerFlagsMask = 0xC0 // extract the qcInfo bits from the header byte |
| 37 | ) |
| 38 | |
| 39 | // Properties provides access to normalization properties of a rune. |
| 40 | type Properties struct { |
| 41 | pos uint8 // start position in reorderBuffer; used in composition.go |
| 42 | size uint8 // length of UTF-8 encoding of this rune |
| 43 | ccc uint8 // leading canonical combining class (ccc if not decomposition) |
| 44 | tccc uint8 // trailing canonical combining class (ccc if not decomposition) |
| 45 | nLead uint8 // number of leading non-starters. |
| 46 | flags qcInfo // quick check flags |
| 47 | index uint16 |
| 48 | } |
| 49 | |
| 50 | // functions dispatchable per form |
| 51 | type lookupFunc func(b input, i int) Properties |
| 52 | |
| 53 | // formInfo holds Form-specific functions and tables. |
| 54 | type formInfo struct { |
| 55 | form Form |
| 56 | composing, compatibility bool // form type |
| 57 | info lookupFunc |
| 58 | nextMain iterFunc |
| 59 | } |
| 60 | |
| 61 | var formTable = []*formInfo{{ |
| 62 | form: NFC, |
| 63 | composing: true, |
| 64 | compatibility: false, |
| 65 | info: lookupInfoNFC, |
| 66 | nextMain: nextComposed, |
| 67 | }, { |
| 68 | form: NFD, |
| 69 | composing: false, |
| 70 | compatibility: false, |
| 71 | info: lookupInfoNFC, |
| 72 | nextMain: nextDecomposed, |
| 73 | }, { |
| 74 | form: NFKC, |
| 75 | composing: true, |
| 76 | compatibility: true, |
| 77 | info: lookupInfoNFKC, |
| 78 | nextMain: nextComposed, |
| 79 | }, { |
| 80 | form: NFKD, |
| 81 | composing: false, |
| 82 | compatibility: true, |
| 83 | info: lookupInfoNFKC, |
| 84 | nextMain: nextDecomposed, |
| 85 | }} |
| 86 | |
| 87 | // We do not distinguish between boundaries for NFC, NFD, etc. to avoid |
| 88 | // unexpected behavior for the user. For example, in NFD, there is a boundary |
| 89 | // after 'a'. However, 'a' might combine with modifiers, so from the application's |
| 90 | // perspective it is not a good boundary. We will therefore always use the |
| 91 | // boundaries for the combining variants. |
| 92 | |
| 93 | // BoundaryBefore returns true if this rune starts a new segment and |
| 94 | // cannot combine with any rune on the left. |
| 95 | func (p Properties) BoundaryBefore() bool { |
| 96 | if p.ccc == 0 && !p.combinesBackward() { |
| 97 | return true |
| 98 | } |
| 99 | // We assume that the CCC of the first character in a decomposition |
| 100 | // is always non-zero if different from info.ccc and that we can return |
| 101 | // false at this point. This is verified by maketables. |
| 102 | return false |
| 103 | } |
| 104 | |
| 105 | // BoundaryAfter returns true if runes cannot combine with or otherwise |
| 106 | // interact with this or previous runes. |
| 107 | func (p Properties) BoundaryAfter() bool { |
| 108 | // TODO: loosen these conditions. |
| 109 | return p.isInert() |
| 110 | } |
| 111 | |
| 112 | // We pack quick check data in 4 bits: |
| 113 | // 5: Combines forward (0 == false, 1 == true) |
| 114 | // 4..3: NFC_QC Yes(00), No (10), or Maybe (11) |
| 115 | // 2: NFD_QC Yes (0) or No (1). No also means there is a decomposition. |
| 116 | // 1..0: Number of trailing non-starters. |
| 117 | // |
| 118 | // When all 4 bits are zero, the character is inert, meaning it is never |
| 119 | // influenced by normalization. |
| 120 | type qcInfo uint8 |
| 121 | |
| 122 | func (p Properties) isYesC() bool { return p.flags&0x10 == 0 } |
| 123 | func (p Properties) isYesD() bool { return p.flags&0x4 == 0 } |
| 124 | |
| 125 | func (p Properties) combinesForward() bool { return p.flags&0x20 != 0 } |
| 126 | func (p Properties) combinesBackward() bool { return p.flags&0x8 != 0 } // == isMaybe |
| 127 | func (p Properties) hasDecomposition() bool { return p.flags&0x4 != 0 } // == isNoD |
| 128 | |
| 129 | func (p Properties) isInert() bool { |
| 130 | return p.flags&qcInfoMask == 0 && p.ccc == 0 |
| 131 | } |
| 132 | |
| 133 | func (p Properties) multiSegment() bool { |
| 134 | return p.index >= firstMulti && p.index < endMulti |
| 135 | } |
| 136 | |
| 137 | func (p Properties) nLeadingNonStarters() uint8 { |
| 138 | return p.nLead |
| 139 | } |
| 140 | |
| 141 | func (p Properties) nTrailingNonStarters() uint8 { |
| 142 | return uint8(p.flags & 0x03) |
| 143 | } |
| 144 | |
| 145 | // Decomposition returns the decomposition for the underlying rune |
| 146 | // or nil if there is none. |
| 147 | func (p Properties) Decomposition() []byte { |
| 148 | // TODO: create the decomposition for Hangul? |
| 149 | if p.index == 0 { |
| 150 | return nil |
| 151 | } |
| 152 | i := p.index |
| 153 | n := decomps[i] & headerLenMask |
| 154 | i++ |
| 155 | return decomps[i : i+uint16(n)] |
| 156 | } |
| 157 | |
| 158 | // Size returns the length of UTF-8 encoding of the rune. |
| 159 | func (p Properties) Size() int { |
| 160 | return int(p.size) |
| 161 | } |
| 162 | |
| 163 | // CCC returns the canonical combining class of the underlying rune. |
| 164 | func (p Properties) CCC() uint8 { |
| 165 | if p.index >= firstCCCZeroExcept { |
| 166 | return 0 |
| 167 | } |
| 168 | return ccc[p.ccc] |
| 169 | } |
| 170 | |
| 171 | // LeadCCC returns the CCC of the first rune in the decomposition. |
| 172 | // If there is no decomposition, LeadCCC equals CCC. |
| 173 | func (p Properties) LeadCCC() uint8 { |
| 174 | return ccc[p.ccc] |
| 175 | } |
| 176 | |
| 177 | // TrailCCC returns the CCC of the last rune in the decomposition. |
| 178 | // If there is no decomposition, TrailCCC equals CCC. |
| 179 | func (p Properties) TrailCCC() uint8 { |
| 180 | return ccc[p.tccc] |
| 181 | } |
| 182 | |
| 183 | func buildRecompMap() { |
| 184 | recompMap = make(map[uint32]rune, len(recompMapPacked)/8) |
| 185 | var buf [8]byte |
| 186 | for i := 0; i < len(recompMapPacked); i += 8 { |
| 187 | copy(buf[:], recompMapPacked[i:i+8]) |
| 188 | key := binary.BigEndian.Uint32(buf[:4]) |
| 189 | val := binary.BigEndian.Uint32(buf[4:]) |
| 190 | recompMap[key] = rune(val) |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | // Recomposition |
| 195 | // We use 32-bit keys instead of 64-bit for the two codepoint keys. |
| 196 | // This clips off the bits of three entries, but we know this will not |
| 197 | // result in a collision. In the unlikely event that changes to |
| 198 | // UnicodeData.txt introduce collisions, the compiler will catch it. |
| 199 | // Note that the recomposition map for NFC and NFKC are identical. |
| 200 | |
| 201 | // combine returns the combined rune or 0 if it doesn't exist. |
| 202 | // |
| 203 | // The caller is responsible for calling |
| 204 | // recompMapOnce.Do(buildRecompMap) sometime before this is called. |
| 205 | func combine(a, b rune) rune { |
| 206 | key := uint32(uint16(a))<<16 + uint32(uint16(b)) |
| 207 | if recompMap == nil { |
| 208 | panic("caller error") // see func comment |
| 209 | } |
| 210 | return recompMap[key] |
| 211 | } |
| 212 | |
| 213 | func lookupInfoNFC(b input, i int) Properties { |
| 214 | v, sz := b.charinfoNFC(i) |
| 215 | return compInfo(v, sz) |
| 216 | } |
| 217 | |
| 218 | func lookupInfoNFKC(b input, i int) Properties { |
| 219 | v, sz := b.charinfoNFKC(i) |
| 220 | return compInfo(v, sz) |
| 221 | } |
| 222 | |
| 223 | // Properties returns properties for the first rune in s. |
| 224 | func (f Form) Properties(s []byte) Properties { |
| 225 | if f == NFC || f == NFD { |
| 226 | return compInfo(nfcData.lookup(s)) |
| 227 | } |
| 228 | return compInfo(nfkcData.lookup(s)) |
| 229 | } |
| 230 | |
| 231 | // PropertiesString returns properties for the first rune in s. |
| 232 | func (f Form) PropertiesString(s string) Properties { |
| 233 | if f == NFC || f == NFD { |
| 234 | return compInfo(nfcData.lookupString(s)) |
| 235 | } |
| 236 | return compInfo(nfkcData.lookupString(s)) |
| 237 | } |
| 238 | |
| 239 | // compInfo converts the information contained in v and sz |
| 240 | // to a Properties. See the comment at the top of the file |
| 241 | // for more information on the format. |
| 242 | func compInfo(v uint16, sz int) Properties { |
| 243 | if v == 0 { |
| 244 | return Properties{size: uint8(sz)} |
| 245 | } else if v >= 0x8000 { |
| 246 | p := Properties{ |
| 247 | size: uint8(sz), |
| 248 | ccc: uint8(v), |
| 249 | tccc: uint8(v), |
| 250 | flags: qcInfo(v >> 8), |
| 251 | } |
| 252 | if p.ccc > 0 || p.combinesBackward() { |
| 253 | p.nLead = uint8(p.flags & 0x3) |
| 254 | } |
| 255 | return p |
| 256 | } |
| 257 | // has decomposition |
| 258 | h := decomps[v] |
| 259 | f := (qcInfo(h&headerFlagsMask) >> 2) | 0x4 |
| 260 | p := Properties{size: uint8(sz), flags: f, index: v} |
| 261 | if v >= firstCCC { |
| 262 | v += uint16(h&headerLenMask) + 1 |
| 263 | c := decomps[v] |
| 264 | p.tccc = c >> 2 |
| 265 | p.flags |= qcInfo(c & 0x3) |
| 266 | if v >= firstLeadingCCC { |
| 267 | p.nLead = c & 0x3 |
| 268 | if v >= firstStarterWithNLead { |
| 269 | // We were tricked. Remove the decomposition. |
| 270 | p.flags &= 0x03 |
| 271 | p.index = 0 |
| 272 | return p |
| 273 | } |
| 274 | p.ccc = decomps[v+1] |
| 275 | } |
| 276 | } |
| 277 | return p |
| 278 | } |