1 /** Implements the Unicode line breaking algorithm. 2 3 The algorithm supports line breaking rules for various languages. 4 */ 5 module linebreak; 6 7 /// 8 @safe nothrow unittest { 9 import std.algorithm : equal, map; 10 11 auto text = "Hello, world!\nThis is an (English) example."; 12 auto broken = text 13 .lineBreakRange 14 .map!(lb => lb.text); 15 16 assert(broken.equal(["Hello, ", "world!\n", "This ", "is ", "an ", "(English) ", "example."])); 17 } 18 19 import std.uni : CodepointTrie, codepointTrie; 20 import std.algorithm.iteration : splitter; 21 22 23 /** Creates a forward range of breakable text segments. 24 25 The returned range of `LineBreak` values, when joined together, makes up the 26 original input string. The `LineBreak.required` property determines whether 27 a certain break is a hard line break or a line break opportunity. 28 */ 29 LineBreakRange!string lineBreakRange(string text) 30 @safe nothrow { 31 return LineBreakRange!string(text); 32 } 33 34 35 /// A range of line break opportunities 36 struct LineBreakRange(R) 37 if (is(R == string)) 38 { 39 private { 40 R m_text; 41 size_t m_pos = 0, m_lastPos = 0; 42 CharClass m_curClass, m_nextClass; 43 LineBreak!R m_curBreak; 44 bool m_empty; 45 } 46 47 this(R text) 48 { 49 m_text = text; 50 if (m_text.length) { 51 m_curBreak = findNextBreak(); 52 m_curBreak.text = m_text[0 .. m_curBreak.index]; 53 } else m_empty = true; 54 } 55 56 /// Determines whether there are no more line breaks left 57 @property bool empty() const { return m_empty; } 58 59 /// The current line break 60 @property ref const(LineBreak!R) front() const { return m_curBreak; } 61 62 /// Returns a snapshot of the range. 63 @property LineBreakRange save() const { return this; } 64 65 /// Advances to the next break opportunity. 66 void popFront() 67 { 68 if (m_curBreak.index >= m_text.length) { 69 m_empty = true; 70 } else { 71 auto sidx = m_curBreak.index; 72 m_curBreak = findNextBreak(); 73 m_curBreak.text = m_text[sidx .. m_curBreak.index]; 74 } 75 } 76 77 private LineBreak!R findNextBreak() 78 { 79 // get the first char if we're at the beginning of the string 80 if (m_curClass == CharClass.none) 81 m_curClass = mapFirst(nextCharClass()); 82 83 while (m_pos < m_text.length) { 84 m_lastPos = m_pos; 85 auto lastClass = m_nextClass; 86 m_nextClass = nextCharClass(); 87 88 // explicit newline 89 if (m_curClass == CharClass.BK || (m_curClass == CharClass.CR && m_nextClass != CharClass.LF)) { 90 m_curClass = mapFirst(mapClass(m_nextClass)); 91 return LineBreak!R(m_lastPos, true); 92 } 93 94 // handle classes not handled by the pair table 95 CharClass cur; 96 switch (m_nextClass) with (CharClass) { 97 default: cur = none; break; 98 case SP: cur = m_curClass; break; 99 case BK, LF, NL: cur = BK; break; 100 case CR: cur = CR; break; 101 case CB: cur = BA; break; 102 } 103 104 if (cur != CharClass.none) { 105 m_curClass = cur; 106 if (m_nextClass == CharClass.CB) 107 return LineBreak!R(m_lastPos); 108 continue; 109 } 110 111 // if not handled already, use the pair table 112 bool shouldBreak = false; 113 assert(m_curClass != CharClass.none); 114 assert(m_nextClass != CharClass.none); 115 switch (pairTable[m_curClass][m_nextClass]) with (Break) { 116 default: break; 117 case DI: // Direct break 118 shouldBreak = true; 119 break; 120 case IN: // possible indirect break 121 shouldBreak = lastClass == CharClass.SP; 122 break; 123 case CI: 124 shouldBreak = lastClass == CharClass.SP; 125 if (!shouldBreak) 126 continue; 127 break; 128 case CP: // prohibited for combining marks 129 if (lastClass != CharClass.SP) 130 continue; 131 break; 132 } 133 134 m_curClass = m_nextClass; 135 if (shouldBreak) 136 return LineBreak!R(m_lastPos); 137 } 138 139 assert (m_pos >= m_text.length); 140 assert (m_lastPos < m_text.length); 141 m_lastPos = m_text.length; 142 return LineBreak!R(m_text.length); 143 } 144 145 private CharClass mapClass(CharClass c) 146 { 147 switch (c) with (CharClass) { 148 case AI: return AL; 149 case SA, SG, XX: return AL; 150 case CJ: return NS; 151 default: return c; 152 } 153 } 154 155 private CharClass mapFirst(CharClass c) 156 { 157 switch (c) with (CharClass) { 158 case LF, NL: return BK; 159 case CB: return BA; 160 case SP: return WJ; 161 default: return c; 162 } 163 } 164 165 private CharClass nextCharClass(bool first = false) 166 { 167 import std.utf : decode; 168 dchar cp; 169 // Not supposed to throw, but returns a replacement char instead: 170 try cp = m_text.decode(m_pos); 171 catch (Exception e) assert(false); 172 173 assert (cp != 0x3002 || getCharacterClass(cp) == CharClass.CL); 174 return mapClass(getCharacterClass(cp)); 175 } 176 } 177 178 unittest { 179 import std.algorithm.comparison : among, equal; 180 import std.algorithm.iteration : filter, map; 181 import std.algorithm.searching : canFind; 182 import std.array : split; 183 import std.conv : parse, to; 184 import std.stdio : File; 185 import std.range : enumerate; 186 import std.string : indexOf, strip; 187 188 // these tests are weird, possibly incorrect or just tailored differently. we skip them. 189 static const skip = [ 190 812, 814, 848, 850, 864, 866, 900, 902, 956, 958, 1068, 1070, 191 1072, 1074, 1224, 1226, 1228, 1230, 1760, 1762, 2932, 2934, 4100, 4101, 192 4102, 4103, 4340, 4342, 4496, 4498, 4568, 4570, 4704, 4706, 4707, 4708, 193 4710, 4711, 4712, 4714, 4715, 4716, 4718, 4719, 4722, 4723, 4726, 4727, 194 4730, 4731, 4734, 4735, 4736, 4738, 4739, 4742, 4743, 4746, 4747, 4748, 195 4750, 4751, 4752, 4754, 4755, 4756, 4758, 4759, 4760, 4762, 4763, 4764, 196 4766, 4767, 4768, 4770, 4771, 4772, 4774, 4775, 4778, 4779, 4780, 4782, 197 4783, 4784, 4786, 4787, 4788, 4790, 4791, 4794, 4795, 4798, 4799, 4800, 198 4802, 4803, 4804, 4806, 4807, 4808, 4810, 4811, 4812, 4814, 4815, 4816, 199 4818, 4819, 4820, 4822, 4823, 4826, 4827, 4830, 4831, 4834, 4835, 4838, 200 4839, 4840, 4842, 4843, 4844, 4846, 4847, 4848, 4850, 4851, 4852, 4854, 201 4855, 4856, 4858, 4859, 4960, 4962, 5036, 5038, 6126, 6135, 6140, 6225, 202 6226, 6227, 6228, 6229, 6230, 6232, 6233, 6234, 6235, 6236, 6332]; 203 204 foreach (i, ln; File("LineBreakTest.txt", "rt").byLine.enumerate) { 205 if (skip.canFind(i)) continue; 206 207 auto hash = ln.indexOf('#'); 208 if (hash >= 0) ln = ln[0 .. hash]; 209 ln = ln.strip(); 210 211 if (!ln.length) 212 continue; 213 214 auto str = ln 215 .split!(ch => ch.among('×', '÷'))[1 .. $-1] 216 .map!((c) { auto cs = c.strip; return cast(dchar)cs.parse!uint(16); }) 217 .to!string; 218 219 auto breaks = lineBreakRange(str) 220 .map!(b => b.text); 221 222 auto expected = ln.split('÷')[0 .. $-1].map!((c) { 223 return c 224 .splitter('×') 225 .filter!(cp => cp.length > 0) 226 .map!((cp) { auto cs = cp.strip; return cast(dchar)cs.parse!uint(16); }) 227 .to!string; 228 }); 229 230 assert(breaks.save.equal(expected)); 231 } 232 } 233 234 235 /** Represents a single line break opprtunity. 236 */ 237 struct LineBreak(R) { 238 /// Code unit index of the breaking point within the input string 239 size_t index; 240 241 /// Determines whether this is a required line break 242 bool required = false; 243 244 /// The text between the preceeding breaking point up to this one 245 R text; 246 247 this(size_t index, bool required = false) 248 { 249 this.index = index; 250 this.required = required; 251 } 252 } 253 254 255 private __gshared typeof(codepointTrie!(CharClass, 8, 5, 8)((CharClass[dchar]).init)) s_characterClasses; 256 257 CharClass getCharacterClass(dchar ch) 258 @trusted nothrow { 259 return s_characterClasses[ch]; 260 } 261 262 shared static this() 263 { 264 import std.algorithm.iteration : filter, map, sum; 265 import std.array : array; 266 import std.conv : parse, to; 267 import std.string : indexOf, strip; 268 import std.typecons : Tuple, tuple; 269 270 static struct ClsDef { 271 CharClass cls; 272 uint min, max; 273 } 274 275 static ClsDef parseLine(string ln) 276 { 277 auto sem = ln.indexOf(';'); 278 auto cls = ln[sem+1 .. $].to!CharClass; 279 ln = ln[0 .. sem]; 280 281 auto rng = ln.indexOf(".."); 282 uint a, b; 283 if (rng >= 0) { 284 string as = ln[0 .. rng]; 285 string bs = ln[rng+2 .. $]; 286 a = as.parse!uint(16); 287 b = bs.parse!uint(16); 288 } else { 289 a = b = ln.parse!uint(16); 290 } 291 return ClsDef(cls, a, b); 292 } 293 294 static immutable defs = import("LineBreak.txt") 295 .splitter('\n') 296 .map!((ln) { 297 auto hash = ln.indexOf('#'); 298 if (hash >= 0) ln = ln[0 .. hash]; 299 return ln.strip(); 300 }) 301 .filter!(ln => ln.length > 0) 302 .map!(ln => parseLine(ln)) 303 .array; 304 305 size_t clscnt = defs.map!(d => d.max - d.min + 1).sum; 306 auto classes = new Tuple!(CharClass, dchar)[](clscnt); 307 308 size_t off = 0; 309 foreach (d; defs) { 310 auto cnt = d.max - d.min + 1; 311 auto dst = classes[off .. off + cnt]; 312 off += cnt; 313 foreach (i; 0 .. cnt) 314 dst[i] = tuple(d.cls, d.min + i); 315 } 316 assert(off == clscnt); 317 318 s_characterClasses = codepointTrie!(CharClass, 8, 5, 8)(classes, CharClass.XX); 319 } 320 321 private enum CharClass { 322 none = -1, 323 // The following break classes are handled by the pair table 324 OP = 0, // Opening punctuation 325 CL = 1, // Closing punctuation 326 CP = 2, // Closing parenthesis 327 QU = 3, // Ambiguous quotation 328 GL = 4, // Glue 329 NS = 5, // Non-starters 330 EX = 6, // Exclamation/Interrogation 331 SY = 7, // Symbols allowing break after 332 IS = 8, // Infix separator 333 PR = 9, // Prefix 334 PO = 10, // Postfix 335 NU = 11, // Numeric 336 AL = 12, // Alphabetic 337 HL = 13, // Hebrew Letter 338 ID = 14, // Ideographic 339 IN = 15, // Inseparable characters 340 HY = 16, // Hyphen 341 BA = 17, // Break after 342 BB = 18, // Break before 343 B2 = 19, // Break on either side (but not pair) 344 ZW = 20, // Zero-width space 345 CM = 21, // Combining marks 346 WJ = 22, // Word joiner 347 H2 = 23, // Hangul LV 348 H3 = 24, // Hangul LVT 349 JL = 25, // Hangul L Jamo 350 JV = 26, // Hangul V Jamo 351 JT = 27, // Hangul T Jamo 352 RI = 28, // Regional Indicator 353 354 // The following break classes are not handled by the pair table 355 AI = 29, // Ambiguous (Alphabetic or Ideograph) 356 BK = 30, // Break (mandatory) 357 CB = 31, // Contingent break 358 CJ = 32, // Conditional Japanese Starter 359 CR = 33, // Carriage return 360 LF = 34, // Line feed 361 NL = 35, // Next line 362 SA = 36, // South-East Asian 363 SG = 37, // Surrogates 364 SP = 38, // Space 365 XX = 39, // Unknown 366 } 367 368 private enum Break { 369 DI = 0, // Direct break opportunity 370 IN = 1, // Indirect break opportunity 371 CI = 2, // Indirect break opportunity for combining marks 372 CP = 3, // Prohibited break for combining marks 373 PR = 4, // Prohibited break 374 } 375 376 // table generated from http://www.unicode.org/reports/tr14/#Table2 377 private static immutable Break[29][29] pairTable = [ 378 [Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.CP, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR, Break.PR], 379 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.PR, Break.IN, Break.IN, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 380 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 381 [Break.PR, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.PR, Break.CI, Break.PR, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN], 382 [Break.IN, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.PR, Break.CI, Break.PR, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN], 383 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 384 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 385 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.IN, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 386 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 387 [Break.IN, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.IN, Break.IN, Break.IN, Break.IN, Break.DI, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.DI], 388 [Break.IN, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 389 [Break.IN, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 390 [Break.IN, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 391 [Break.IN, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 392 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.IN, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 393 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 394 [Break.DI, Break.PR, Break.PR, Break.IN, Break.DI, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.IN, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 395 [Break.DI, Break.PR, Break.PR, Break.IN, Break.DI, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 396 [Break.IN, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.PR, Break.CI, Break.PR, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN], 397 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.DI, Break.PR, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 398 [Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 399 [Break.IN, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI], 400 [Break.IN, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.PR, Break.CI, Break.PR, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN, Break.IN], 401 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.IN, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.DI], 402 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.IN, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.DI], 403 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.IN, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.IN, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI], 404 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.IN, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.DI], 405 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.IN, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.DI], 406 [Break.DI, Break.PR, Break.PR, Break.IN, Break.IN, Break.IN, Break.PR, Break.PR, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN, Break.IN, Break.DI, Break.DI, Break.PR, Break.CI, Break.PR, Break.DI, Break.DI, Break.DI, Break.DI, Break.DI, Break.IN] 407 ];