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 ];