diff --git a/Lib/test/test_unicodedata.py b/Lib/test/test_unicodedata.py index aeb1835f6cc..256a010e8fd 100644 --- a/Lib/test/test_unicodedata.py +++ b/Lib/test/test_unicodedata.py @@ -107,7 +107,7 @@ def test_method_checksum(self): class BaseUnicodeFunctionsTest: - @unittest.skip # TODO: RUSTPYTHON; AssertionError: '7bda75e48a961a01ab328358980cefc5c0a1666d' != '68cd01e2c680b851c1fcab012efb5635' + @unittest.skip("TODO: RUSTPYTHON; AssertionError: '7bda75e48a961a01ab328358980cefc5c0a1666d' != '68cd01e2c680b851c1fcab012efb5635'") def test_function_checksum(self): db = self.db data = [] @@ -199,7 +199,7 @@ def test_no_names_in_pua(self): char = chr(i) self.assertRaises(ValueError, self.db.name, char) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: KeyError not raised by lookup + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: KeyError not raised by lookup def test_lookup_nonexistant(self): # just make sure that lookup can fail for nonexistent in [ @@ -360,7 +360,7 @@ def test_bidirectional_unassigned(self): self.assertEqual(self.db.bidirectional('\U0001FFFE'), '' if self.old else 'BN') self.assertEqual(self.db.bidirectional('\U00010D01'), '' if self.old else 'AL') - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ' 03A3 != '' + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ' 03A3 != '' def test_decomposition(self): self.assertEqual(self.db.decomposition('\uFFFE'),'') self.assertEqual(self.db.decomposition('\u00bc'), ' 0031 2044 0034') @@ -700,7 +700,7 @@ def test_east_asian_width(self): # New in 17.0.0 self.assertEqual(eaw('\U00016FF2'), 'N' if self.old else 'W') - @unittest.skip # TODO: RUSTPYTHON; AssertionError: 'N' != 'W' (passed on latest, fails on 3.2) + @unittest.skip("TODO: RUSTPYTHON; AssertionError: 'N' != 'W' (passed on latest, fails on 3.2)") def test_east_asian_width_unassigned(self): eaw = self.db.east_asian_width # unassigned @@ -769,7 +769,7 @@ def parse_cp(s): for cp in range(0, sys.maxunicode + 1): self.assertEqual(self.db.name(chr(cp), None), names_ref.get(cp)) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'isxidstart' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'isxidstart' def test_isxidstart(self): self.assertTrue(self.db.isxidstart('S')) self.assertTrue(self.db.isxidstart('\u0AD0')) # GUJARATI OM @@ -783,7 +783,7 @@ def test_isxidstart(self): self.assertRaises(TypeError, self.db.isxidstart) self.assertRaises(TypeError, self.db.isxidstart, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'isxidcontinue' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'isxidcontinue' def test_isxidcontinue(self): self.assertTrue(self.db.isxidcontinue('S')) self.assertTrue(self.db.isxidcontinue('_')) @@ -798,7 +798,6 @@ def test_isxidcontinue(self): self.assertRaises(TypeError, self.db.isxidcontinue) self.assertRaises(TypeError, self.db.isxidcontinue, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'grapheme_cluster_break' def test_grapheme_cluster_break(self): gcb = self.db.grapheme_cluster_break self.assertEqual(gcb(' '), 'Other') @@ -896,7 +895,6 @@ def test_grapheme_cluster_break(self): self.assertRaises(TypeError, gcb, '') self.assertRaises(TypeError, gcb, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'indic_conjunct_break' def test_indic_conjunct_break(self): incb = self.db.indic_conjunct_break self.assertEqual(incb(' '), 'None') @@ -925,7 +923,6 @@ def test_indic_conjunct_break(self): self.assertRaises(TypeError, incb, '') self.assertRaises(TypeError, incb, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'extended_pictographic' def test_extended_pictographic(self): ext_pict = self.db.extended_pictographic self.assertIs(ext_pict(' '), False) @@ -946,7 +943,6 @@ def test_extended_pictographic(self): self.assertRaises(TypeError, ext_pict, '') self.assertRaises(TypeError, ext_pict, 'xx') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'iter_graphemes' def test_grapheme_break(self): def graphemes(*args): return list(map(str, self.db.iter_graphemes(*args))) @@ -1017,7 +1013,7 @@ def graphemes(*args): 'a\U0001F1FA\U0001F1E6\U0001F1FA\U0001F1F3'), ['a', '\U0001F1FA\U0001F1E6', '\U0001F1FA\U0001F1F3']) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'block' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'block' def test_block(self): self.assertEqual(self.db.block('\u0000'), 'Basic Latin') self.assertEqual(self.db.block('\u0041'), 'Basic Latin') @@ -1102,7 +1098,7 @@ def test_block(self): self.assertEqual(self.db.block('\U00100000'), 'Supplementary Private Use Area-B') self.assertEqual(self.db.block('\U0010FFFF'), 'Supplementary Private Use Area-B') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'block' + @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'block' def test_block_invalid_input(self): self.assertRaises(TypeError, self.db.block) self.assertRaises(TypeError, self.db.block, b'x') @@ -1169,7 +1165,7 @@ def test_failed_import_during_compiling(self): "(can't load unicodedata module)" self.assertIn(error, result.err.decode("ascii")) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError Process return code is 1 + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError Process return code is 1 def test_unicodedata_unload_reload(self): # gh-149449: dropping unicodedata and running gc must not leave the # cached _ucnhash_CAPI pointer dangling. @@ -1262,7 +1258,6 @@ def test_linebreak_7643(self): self.assertEqual(len(lines), 1, r"%a should not be a linebreak" % c) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'iter_graphemes' def test_segment_object(self): segments = list(unicodedata.iter_graphemes('spa\u0300m')) self.assertEqual(len(segments), 4, segments) @@ -1384,7 +1379,6 @@ class MyStr(str): class GraphemeBreakTest(unittest.TestCase): @requires_resource('network') - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError module 'unicodedata' has no attribute 'iter_graphemes' def test_grapheme_break(self): TESTDATAFILE = "GraphemeBreakTest.txt" testdata = download_test_data_file(TESTDATAFILE) @@ -1423,7 +1417,6 @@ def run_grapheme_break_tests(self, testdata): self.assertEqual([x.start for x in result], breaks[i:-1], comment) self.assertEqual([x.end for x in result], breaks[i+1:], comment) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'unicodedata' has no attribute 'iter_graphemes' def test_reference_loops(self): # Test that reference loops involving GraphemeBreakIterator or # Segment can be broken by the garbage collector. diff --git a/benches/execution.rs b/benches/execution.rs index a09ab9c7890..c0c783f24ed 100644 --- a/benches/execution.rs +++ b/benches/execution.rs @@ -7,7 +7,7 @@ use criterion::{ Bencher, BenchmarkGroup, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main, measurement::WallTime, }; -use pyo3::types::{PyCode, PyCodeInput, PyCodeMethods, PyDict, PyDictMethods}; +use pyo3::types::{PyCode, PyCodeMethods, PyDict, PyDictMethods, code::PyCodeInput}; use rustpython_compiler::Mode; use rustpython_vm::{Interpreter, PyResult, Settings}; use std::{collections::HashMap, path::Path}; diff --git a/crates/stdlib/src/unicodedata.rs b/crates/stdlib/src/unicodedata.rs index e5ef959c66c..ab6d808e58a 100644 --- a/crates/stdlib/src/unicodedata.rs +++ b/crates/stdlib/src/unicodedata.rs @@ -35,10 +35,187 @@ mod unicodedata { use crate::vm::{ Py, PyObjectRef, PyPayload, PyResult, VirtualMachine, builtins::{PyModule, PyStr, PyStrRef}, + common::lock::PyMutex, function::OptionalArg, + protocol::PyIterReturn, + types::{IterNext, Iterable, Representable, SelfIter}, }; use itertools::Itertools; use rustpython_common::wtf8::{CodePoint, Wtf8Buf}; + use unicode_core::grapheme::{self, GraphemeBreakState}; + + fn grapheme_char(character: &Py, vm: &VirtualMachine) -> PyResult { + character + .as_wtf8() + .code_points() + .exactly_one() + .map_err(|_| vm.new_type_error("argument must be a unicode character, not str")) + } + + #[pyfunction] + fn grapheme_cluster_break(chr: PyStrRef, vm: &VirtualMachine) -> PyResult<&'static str> { + grapheme_char(&chr, vm).map(grapheme::grapheme_cluster_break) + } + + #[pyfunction] + fn indic_conjunct_break(chr: PyStrRef, vm: &VirtualMachine) -> PyResult<&'static str> { + grapheme_char(&chr, vm).map(grapheme::indic_conjunct_break) + } + + #[pyfunction] + fn extended_pictographic(chr: PyStrRef, vm: &VirtualMachine) -> PyResult { + grapheme_char(&chr, vm).map(grapheme::extended_pictographic) + } + + #[pyclass(no_attr, name = "Segment", traverse)] + #[derive(Debug, PyPayload)] + struct Segment { + string: PyStrRef, + #[pytraverse(skip)] + start: usize, + #[pytraverse(skip)] + end: usize, + #[pytraverse(skip)] + bytes: core::ops::Range, + } + + #[pyclass(with(Representable), flags(IMMUTABLETYPE, DISALLOW_INSTANTIATION))] + impl Segment { + #[pygetset] + fn start(zelf: &Py) -> usize { + zelf.start + } + + #[pygetset] + fn end(zelf: &Py) -> usize { + zelf.end + } + + #[pymethod] + fn __str__(zelf: &Py, vm: &VirtualMachine) -> PyStrRef { + vm.ctx.new_str(&zelf.string.as_wtf8()[zelf.bytes.clone()]) + } + } + + impl Representable for Segment { + #[inline] + fn repr_str(zelf: &Py, _vm: &VirtualMachine) -> PyResult { + Ok(format!("", zelf.start, zelf.end)) + } + } + + #[derive(Debug)] + struct GraphemePosition { + start: usize, + start_byte: usize, + pos: usize, + pos_byte: usize, + end: usize, + end_byte: usize, + state: GraphemeBreakState, + } + + #[pyclass(no_attr, name = "GraphemeBreakIterator", traverse)] + #[derive(Debug, PyPayload)] + struct GraphemeBreakIterator { + string: PyStrRef, + #[pytraverse(skip)] + position: PyMutex, + } + + #[pyclass(with(IterNext, Iterable), flags(IMMUTABLETYPE, DISALLOW_INSTANTIATION))] + impl GraphemeBreakIterator {} + + impl SelfIter for GraphemeBreakIterator {} + + impl IterNext for GraphemeBreakIterator { + fn next(zelf: &Py, vm: &VirtualMachine) -> PyResult { + let mut position = zelf.position.lock(); + if position.start >= position.end { + return Ok(PyIterReturn::StopIteration(None)); + } + let start = position.start; + let start_byte = position.start_byte; + let scan_byte = position.pos_byte; + let mut points = zelf.string.as_wtf8()[scan_byte..position.end_byte] + .code_point_indices() + .peekable(); + let (end, end_byte) = loop { + let Some((offset, point)) = points.next() else { + position.start = position.pos; + position.start_byte = position.end_byte; + break (position.pos, position.end_byte); + }; + let byte = scan_byte + offset; + let index = position.pos; + let boundary = position.state.advance(point); + position.pos += 1; + position.pos_byte = points + .peek() + .map_or(position.end_byte, |(offset, _)| scan_byte + offset); + if boundary { + position.start = index; + position.start_byte = byte; + break (index, byte); + } + }; + drop(position); + Ok(PyIterReturn::Return( + Segment { + string: zelf.string.clone(), + start, + end, + bytes: start_byte..end_byte, + } + .into_ref(&vm.ctx) + .into(), + )) + } + } + + #[pyfunction] + fn iter_graphemes( + unistr: PyStrRef, + start: OptionalArg, + end: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult> { + // Convert in argument order, including overflow, before adjusting bounds. + let index = |arg: OptionalArg, default| -> PyResult { + match arg { + OptionalArg::Present(value) => value.try_index(vm)?.try_to_primitive(vm), + OptionalArg::Missing => Ok(default), + } + }; + let mut start = index(start, 0)?; + let mut end = index(end, isize::MAX)?; + let len = unistr.char_len() as isize; + if end > len { + end = len; + } else if end < 0 { + end = (end + len).max(0); + } + if start < 0 { + start = (start + len).max(0); + } + let start = start as usize; + let end = end as usize; + let start_byte = unistr.char_index_to_byte(start); + let end_byte = unistr.char_index_to_byte(end); + Ok(GraphemeBreakIterator { + string: unistr, + position: PyMutex::new(GraphemePosition { + start, + start_byte, + pos: start, + pos_byte: start_byte, + end, + end_byte, + state: GraphemeBreakState::default(), + }), + } + .into_ref(&vm.ctx)) + } pub(crate) fn module_exec(vm: &VirtualMachine, module: &Py) -> PyResult<()> { __module_exec(vm, module); diff --git a/crates/unicode/src/grapheme.rs b/crates/unicode/src/grapheme.rs new file mode 100644 index 00000000000..b8a097e3710 --- /dev/null +++ b/crates/unicode/src/grapheme.rs @@ -0,0 +1,180 @@ +//! Extended grapheme clusters over Python's full code point domain. +//! +//! State transitions and rule ordering follow CPython's Modules/unicodedata.c +//! at d220409a13b77de7a6c30abe16ea90db01528edc (Unicode 17.0.0). + +// spell-checker:ignore incb + +use icu_properties::{ + CodePointMapData, CodePointSetData, + props::{ExtendedPictographic, GraphemeClusterBreak as Gcb, IndicConjunctBreak as Incb}, +}; +use rustpython_wtf8::CodePoint; + +fn gcb(c: CodePoint) -> Gcb { + CodePointMapData::::new().get32(c.to_u32()) +} + +fn incb(c: CodePoint) -> Incb { + CodePointMapData::::new().get32(c.to_u32()) +} + +/// The long Grapheme_Cluster_Break property name. +#[must_use] +pub fn grapheme_cluster_break(c: CodePoint) -> &'static str { + match gcb(c) { + Gcb::CR => "CR", + Gcb::LF => "LF", + Gcb::Control => "Control", + Gcb::Extend => "Extend", + Gcb::L => "L", + Gcb::V => "V", + Gcb::T => "T", + Gcb::LV => "LV", + Gcb::LVT => "LVT", + Gcb::SpacingMark => "SpacingMark", + Gcb::Prepend => "Prepend", + Gcb::RegionalIndicator => "Regional_Indicator", + Gcb::ZWJ => "ZWJ", + _ => "Other", + } +} + +/// The long Indic_Conjunct_Break property name. +#[must_use] +pub fn indic_conjunct_break(c: CodePoint) -> &'static str { + match incb(c) { + Incb::Consonant => "Consonant", + Incb::Extend => "Extend", + Incb::Linker => "Linker", + _ => "None", + } +} + +/// Whether the code point has the Extended_Pictographic property. +#[must_use] +pub fn extended_pictographic(c: CodePoint) -> bool { + CodePointSetData::new::().contains32(c.to_u32()) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ExtPictState { + Init, + Started, + Zwj, + Matched, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum IncbState { + Init, + Started, + Linker, + Matched, +} + +/// Streaming break state, initialized afresh at the requested slice start. +/// +/// A code point is consumed before its preceding boundary is returned. Keep +/// this state across cluster yields, rather than processing that point twice. +#[derive(Debug)] +pub struct GraphemeBreakState { + previous: Gcb, + pictographic: ExtPictState, + conjunct: IncbState, + regional_odd: bool, + started: bool, +} + +impl Default for GraphemeBreakState { + fn default() -> Self { + Self { + previous: Gcb::Other, + pictographic: ExtPictState::Init, + conjunct: IncbState::Init, + regional_odd: false, + started: false, + } + } +} + +impl GraphemeBreakState { + /// Consume one code point and report whether a new cluster starts here. + pub fn advance(&mut self, c: CodePoint) -> bool { + let current = gcb(c); + self.pictographic = if extended_pictographic(c) { + if self.pictographic == ExtPictState::Zwj { + ExtPictState::Matched + } else { + ExtPictState::Started + } + } else if matches!( + self.pictographic, + ExtPictState::Started | ExtPictState::Matched + ) { + match current { + Gcb::Extend => ExtPictState::Started, + Gcb::ZWJ => ExtPictState::Zwj, + _ => ExtPictState::Init, + } + } else { + ExtPictState::Init + }; + self.conjunct = match incb(c) { + Incb::Consonant => { + if self.conjunct == IncbState::Linker { + IncbState::Matched + } else { + IncbState::Started + } + } + Incb::Extend if self.conjunct != IncbState::Init => { + if self.conjunct == IncbState::Linker { + IncbState::Linker + } else { + IncbState::Started + } + } + Incb::Linker if self.conjunct != IncbState::Init => IncbState::Linker, + _ => IncbState::Init, + }; + self.regional_odd = current == Gcb::RegionalIndicator && !self.regional_odd; + let boundary = self.started && self.is_break(current); + self.previous = current; + self.started = true; + boundary + } + + fn is_break(&self, current: Gcb) -> bool { + let previous = self.previous; + // GB3, GB4, GB5: controls take precedence over attachment rules. + if previous == Gcb::CR && current == Gcb::LF { + return false; + } + if matches!(previous, Gcb::CR | Gcb::LF | Gcb::Control) + || matches!(current, Gcb::CR | Gcb::LF | Gcb::Control) + { + return true; + } + // GB6, GB7, GB8: Hangul syllables. + if previous == Gcb::L && matches!(current, Gcb::L | Gcb::V | Gcb::LV | Gcb::LVT) + || matches!(previous, Gcb::LV | Gcb::V) && matches!(current, Gcb::V | Gcb::T) + || matches!(previous, Gcb::LVT | Gcb::T) && current == Gcb::T + { + return false; + } + // GB9, GB9a, GB9b, GB9c, GB11. + if matches!(current, Gcb::Extend | Gcb::ZWJ | Gcb::SpacingMark) + || previous == Gcb::Prepend + || self.conjunct == IncbState::Matched + || self.pictographic == ExtPictState::Matched + { + return false; + } + // GB12, GB13: parity includes the current regional indicator. + if previous == Gcb::RegionalIndicator && current == previous { + return self.regional_odd; + } + true // GB999 + } +} diff --git a/crates/unicode/src/lib.rs b/crates/unicode/src/lib.rs index 319271b019e..fef618dfe17 100644 --- a/crates/unicode/src/lib.rs +++ b/crates/unicode/src/lib.rs @@ -12,6 +12,7 @@ extern crate alloc; pub mod case; pub mod classify; pub mod data; +pub mod grapheme; pub mod identifier; pub mod normalize; diff --git a/crates/unicode/tests/data/cpython3.15_graphemes.txt b/crates/unicode/tests/data/cpython3.15_graphemes.txt new file mode 100644 index 00000000000..78de1d3824c --- /dev/null +++ b/crates/unicode/tests/data/cpython3.15_graphemes.txt @@ -0,0 +1,2171 @@ +# CPython d220409a13b77de7a6c30abe16ea90db01528edc; Unicode 17.0.0 +# Inclusive contiguous ranges: start:end GCB InCB Extended_Pictographic +# Reconstructed raw SHA256 9aae5520167e69e08d2241b9899bc7c65b0865d1713ffde698a6e7e1c952284d +000000:000009 Control None 0 +00000A:00000A LF None 0 +00000B:00000C Control None 0 +00000D:00000D CR None 0 +00000E:00001F Control None 0 +000020:00007E Other None 0 +00007F:00009F Control None 0 +0000A0:0000A8 Other None 0 +0000A9:0000A9 Other None 1 +0000AA:0000AC Other None 0 +0000AD:0000AD Control None 0 +0000AE:0000AE Other None 1 +0000AF:0002FF Other None 0 +000300:00036F Extend Extend 0 +000370:000482 Other None 0 +000483:000489 Extend Extend 0 +00048A:000590 Other None 0 +000591:0005BD Extend Extend 0 +0005BE:0005BE Other None 0 +0005BF:0005BF Extend Extend 0 +0005C0:0005C0 Other None 0 +0005C1:0005C2 Extend Extend 0 +0005C3:0005C3 Other None 0 +0005C4:0005C5 Extend Extend 0 +0005C6:0005C6 Other None 0 +0005C7:0005C7 Extend Extend 0 +0005C8:0005FF Other None 0 +000600:000605 Prepend None 0 +000606:00060F Other None 0 +000610:00061A Extend Extend 0 +00061B:00061B Other None 0 +00061C:00061C Control None 0 +00061D:00064A Other None 0 +00064B:00065F Extend Extend 0 +000660:00066F Other None 0 +000670:000670 Extend Extend 0 +000671:0006D5 Other None 0 +0006D6:0006DC Extend Extend 0 +0006DD:0006DD Prepend None 0 +0006DE:0006DE Other None 0 +0006DF:0006E4 Extend Extend 0 +0006E5:0006E6 Other None 0 +0006E7:0006E8 Extend Extend 0 +0006E9:0006E9 Other None 0 +0006EA:0006ED Extend Extend 0 +0006EE:00070E Other None 0 +00070F:00070F Prepend None 0 +000710:000710 Other None 0 +000711:000711 Extend Extend 0 +000712:00072F Other None 0 +000730:00074A Extend Extend 0 +00074B:0007A5 Other None 0 +0007A6:0007B0 Extend Extend 0 +0007B1:0007EA Other None 0 +0007EB:0007F3 Extend Extend 0 +0007F4:0007FC Other None 0 +0007FD:0007FD Extend Extend 0 +0007FE:000815 Other None 0 +000816:000819 Extend Extend 0 +00081A:00081A Other None 0 +00081B:000823 Extend Extend 0 +000824:000824 Other None 0 +000825:000827 Extend Extend 0 +000828:000828 Other None 0 +000829:00082D Extend Extend 0 +00082E:000858 Other None 0 +000859:00085B Extend Extend 0 +00085C:00088F Other None 0 +000890:000891 Prepend None 0 +000892:000896 Other None 0 +000897:00089F Extend Extend 0 +0008A0:0008C9 Other None 0 +0008CA:0008E1 Extend Extend 0 +0008E2:0008E2 Prepend None 0 +0008E3:000902 Extend Extend 0 +000903:000903 SpacingMark None 0 +000904:000914 Other None 0 +000915:000939 Other Consonant 0 +00093A:00093A Extend Extend 0 +00093B:00093B SpacingMark None 0 +00093C:00093C Extend Extend 0 +00093D:00093D Other None 0 +00093E:000940 SpacingMark None 0 +000941:000948 Extend Extend 0 +000949:00094C SpacingMark None 0 +00094D:00094D Extend Linker 0 +00094E:00094F SpacingMark None 0 +000950:000950 Other None 0 +000951:000957 Extend Extend 0 +000958:00095F Other Consonant 0 +000960:000961 Other None 0 +000962:000963 Extend Extend 0 +000964:000977 Other None 0 +000978:00097F Other Consonant 0 +000980:000980 Other None 0 +000981:000981 Extend Extend 0 +000982:000983 SpacingMark None 0 +000984:000994 Other None 0 +000995:0009A8 Other Consonant 0 +0009A9:0009A9 Other None 0 +0009AA:0009B0 Other Consonant 0 +0009B1:0009B1 Other None 0 +0009B2:0009B2 Other Consonant 0 +0009B3:0009B5 Other None 0 +0009B6:0009B9 Other Consonant 0 +0009BA:0009BB Other None 0 +0009BC:0009BC Extend Extend 0 +0009BD:0009BD Other None 0 +0009BE:0009BE Extend Extend 0 +0009BF:0009C0 SpacingMark None 0 +0009C1:0009C4 Extend Extend 0 +0009C5:0009C6 Other None 0 +0009C7:0009C8 SpacingMark None 0 +0009C9:0009CA Other None 0 +0009CB:0009CC SpacingMark None 0 +0009CD:0009CD Extend Linker 0 +0009CE:0009D6 Other None 0 +0009D7:0009D7 Extend Extend 0 +0009D8:0009DB Other None 0 +0009DC:0009DD Other Consonant 0 +0009DE:0009DE Other None 0 +0009DF:0009DF Other Consonant 0 +0009E0:0009E1 Other None 0 +0009E2:0009E3 Extend Extend 0 +0009E4:0009EF Other None 0 +0009F0:0009F1 Other Consonant 0 +0009F2:0009FD Other None 0 +0009FE:0009FE Extend Extend 0 +0009FF:000A00 Other None 0 +000A01:000A02 Extend Extend 0 +000A03:000A03 SpacingMark None 0 +000A04:000A3B Other None 0 +000A3C:000A3C Extend Extend 0 +000A3D:000A3D Other None 0 +000A3E:000A40 SpacingMark None 0 +000A41:000A42 Extend Extend 0 +000A43:000A46 Other None 0 +000A47:000A48 Extend Extend 0 +000A49:000A4A Other None 0 +000A4B:000A4D Extend Extend 0 +000A4E:000A50 Other None 0 +000A51:000A51 Extend Extend 0 +000A52:000A6F Other None 0 +000A70:000A71 Extend Extend 0 +000A72:000A74 Other None 0 +000A75:000A75 Extend Extend 0 +000A76:000A80 Other None 0 +000A81:000A82 Extend Extend 0 +000A83:000A83 SpacingMark None 0 +000A84:000A94 Other None 0 +000A95:000AA8 Other Consonant 0 +000AA9:000AA9 Other None 0 +000AAA:000AB0 Other Consonant 0 +000AB1:000AB1 Other None 0 +000AB2:000AB3 Other Consonant 0 +000AB4:000AB4 Other None 0 +000AB5:000AB9 Other Consonant 0 +000ABA:000ABB Other None 0 +000ABC:000ABC Extend Extend 0 +000ABD:000ABD Other None 0 +000ABE:000AC0 SpacingMark None 0 +000AC1:000AC5 Extend Extend 0 +000AC6:000AC6 Other None 0 +000AC7:000AC8 Extend Extend 0 +000AC9:000AC9 SpacingMark None 0 +000ACA:000ACA Other None 0 +000ACB:000ACC SpacingMark None 0 +000ACD:000ACD Extend Linker 0 +000ACE:000AE1 Other None 0 +000AE2:000AE3 Extend Extend 0 +000AE4:000AF8 Other None 0 +000AF9:000AF9 Other Consonant 0 +000AFA:000AFF Extend Extend 0 +000B00:000B00 Other None 0 +000B01:000B01 Extend Extend 0 +000B02:000B03 SpacingMark None 0 +000B04:000B14 Other None 0 +000B15:000B28 Other Consonant 0 +000B29:000B29 Other None 0 +000B2A:000B30 Other Consonant 0 +000B31:000B31 Other None 0 +000B32:000B33 Other Consonant 0 +000B34:000B34 Other None 0 +000B35:000B39 Other Consonant 0 +000B3A:000B3B Other None 0 +000B3C:000B3C Extend Extend 0 +000B3D:000B3D Other None 0 +000B3E:000B3F Extend Extend 0 +000B40:000B40 SpacingMark None 0 +000B41:000B44 Extend Extend 0 +000B45:000B46 Other None 0 +000B47:000B48 SpacingMark None 0 +000B49:000B4A Other None 0 +000B4B:000B4C SpacingMark None 0 +000B4D:000B4D Extend Linker 0 +000B4E:000B54 Other None 0 +000B55:000B57 Extend Extend 0 +000B58:000B5B Other None 0 +000B5C:000B5D Other Consonant 0 +000B5E:000B5E Other None 0 +000B5F:000B5F Other Consonant 0 +000B60:000B61 Other None 0 +000B62:000B63 Extend Extend 0 +000B64:000B70 Other None 0 +000B71:000B71 Other Consonant 0 +000B72:000B81 Other None 0 +000B82:000B82 Extend Extend 0 +000B83:000BBD Other None 0 +000BBE:000BBE Extend Extend 0 +000BBF:000BBF SpacingMark None 0 +000BC0:000BC0 Extend Extend 0 +000BC1:000BC2 SpacingMark None 0 +000BC3:000BC5 Other None 0 +000BC6:000BC8 SpacingMark None 0 +000BC9:000BC9 Other None 0 +000BCA:000BCC SpacingMark None 0 +000BCD:000BCD Extend Extend 0 +000BCE:000BD6 Other None 0 +000BD7:000BD7 Extend Extend 0 +000BD8:000BFF Other None 0 +000C00:000C00 Extend Extend 0 +000C01:000C03 SpacingMark None 0 +000C04:000C04 Extend Extend 0 +000C05:000C14 Other None 0 +000C15:000C28 Other Consonant 0 +000C29:000C29 Other None 0 +000C2A:000C39 Other Consonant 0 +000C3A:000C3B Other None 0 +000C3C:000C3C Extend Extend 0 +000C3D:000C3D Other None 0 +000C3E:000C40 Extend Extend 0 +000C41:000C44 SpacingMark None 0 +000C45:000C45 Other None 0 +000C46:000C48 Extend Extend 0 +000C49:000C49 Other None 0 +000C4A:000C4C Extend Extend 0 +000C4D:000C4D Extend Linker 0 +000C4E:000C54 Other None 0 +000C55:000C56 Extend Extend 0 +000C57:000C57 Other None 0 +000C58:000C5A Other Consonant 0 +000C5B:000C61 Other None 0 +000C62:000C63 Extend Extend 0 +000C64:000C80 Other None 0 +000C81:000C81 Extend Extend 0 +000C82:000C83 SpacingMark None 0 +000C84:000CBB Other None 0 +000CBC:000CBC Extend Extend 0 +000CBD:000CBD Other None 0 +000CBE:000CBE SpacingMark None 0 +000CBF:000CC0 Extend Extend 0 +000CC1:000CC1 SpacingMark None 0 +000CC2:000CC2 Extend Extend 0 +000CC3:000CC4 SpacingMark None 0 +000CC5:000CC5 Other None 0 +000CC6:000CC8 Extend Extend 0 +000CC9:000CC9 Other None 0 +000CCA:000CCD Extend Extend 0 +000CCE:000CD4 Other None 0 +000CD5:000CD6 Extend Extend 0 +000CD7:000CE1 Other None 0 +000CE2:000CE3 Extend Extend 0 +000CE4:000CF2 Other None 0 +000CF3:000CF3 SpacingMark None 0 +000CF4:000CFF Other None 0 +000D00:000D01 Extend Extend 0 +000D02:000D03 SpacingMark None 0 +000D04:000D14 Other None 0 +000D15:000D3A Other Consonant 0 +000D3B:000D3C Extend Extend 0 +000D3D:000D3D Other None 0 +000D3E:000D3E Extend Extend 0 +000D3F:000D40 SpacingMark None 0 +000D41:000D44 Extend Extend 0 +000D45:000D45 Other None 0 +000D46:000D48 SpacingMark None 0 +000D49:000D49 Other None 0 +000D4A:000D4C SpacingMark None 0 +000D4D:000D4D Extend Linker 0 +000D4E:000D4E Prepend None 0 +000D4F:000D56 Other None 0 +000D57:000D57 Extend Extend 0 +000D58:000D61 Other None 0 +000D62:000D63 Extend Extend 0 +000D64:000D80 Other None 0 +000D81:000D81 Extend Extend 0 +000D82:000D83 SpacingMark None 0 +000D84:000DC9 Other None 0 +000DCA:000DCA Extend Extend 0 +000DCB:000DCE Other None 0 +000DCF:000DCF Extend Extend 0 +000DD0:000DD1 SpacingMark None 0 +000DD2:000DD4 Extend Extend 0 +000DD5:000DD5 Other None 0 +000DD6:000DD6 Extend Extend 0 +000DD7:000DD7 Other None 0 +000DD8:000DDE SpacingMark None 0 +000DDF:000DDF Extend Extend 0 +000DE0:000DF1 Other None 0 +000DF2:000DF3 SpacingMark None 0 +000DF4:000E30 Other None 0 +000E31:000E31 Extend Extend 0 +000E32:000E32 Other None 0 +000E33:000E33 SpacingMark None 0 +000E34:000E3A Extend Extend 0 +000E3B:000E46 Other None 0 +000E47:000E4E Extend Extend 0 +000E4F:000EB0 Other None 0 +000EB1:000EB1 Extend Extend 0 +000EB2:000EB2 Other None 0 +000EB3:000EB3 SpacingMark None 0 +000EB4:000EBC Extend Extend 0 +000EBD:000EC7 Other None 0 +000EC8:000ECE Extend Extend 0 +000ECF:000F17 Other None 0 +000F18:000F19 Extend Extend 0 +000F1A:000F34 Other None 0 +000F35:000F35 Extend Extend 0 +000F36:000F36 Other None 0 +000F37:000F37 Extend Extend 0 +000F38:000F38 Other None 0 +000F39:000F39 Extend Extend 0 +000F3A:000F3D Other None 0 +000F3E:000F3F SpacingMark None 0 +000F40:000F70 Other None 0 +000F71:000F7E Extend Extend 0 +000F7F:000F7F SpacingMark None 0 +000F80:000F84 Extend Extend 0 +000F85:000F85 Other None 0 +000F86:000F87 Extend Extend 0 +000F88:000F8C Other None 0 +000F8D:000F97 Extend Extend 0 +000F98:000F98 Other None 0 +000F99:000FBC Extend Extend 0 +000FBD:000FC5 Other None 0 +000FC6:000FC6 Extend Extend 0 +000FC7:000FFF Other None 0 +001000:00102A Other Consonant 0 +00102B:00102C Other None 0 +00102D:001030 Extend Extend 0 +001031:001031 SpacingMark None 0 +001032:001037 Extend Extend 0 +001038:001038 Other None 0 +001039:001039 Extend Linker 0 +00103A:00103A Extend Extend 0 +00103B:00103C SpacingMark None 0 +00103D:00103E Extend Extend 0 +00103F:00103F Other Consonant 0 +001040:00104F Other None 0 +001050:001055 Other Consonant 0 +001056:001057 SpacingMark None 0 +001058:001059 Extend Extend 0 +00105A:00105D Other Consonant 0 +00105E:001060 Extend Extend 0 +001061:001061 Other Consonant 0 +001062:001064 Other None 0 +001065:001066 Other Consonant 0 +001067:00106D Other None 0 +00106E:001070 Other Consonant 0 +001071:001074 Extend Extend 0 +001075:001081 Other Consonant 0 +001082:001082 Extend Extend 0 +001083:001083 Other None 0 +001084:001084 SpacingMark None 0 +001085:001086 Extend Extend 0 +001087:00108C Other None 0 +00108D:00108D Extend Extend 0 +00108E:00108E Other Consonant 0 +00108F:00109C Other None 0 +00109D:00109D Extend Extend 0 +00109E:0010FF Other None 0 +001100:00115F L None 0 +001160:0011A7 V None 0 +0011A8:0011FF T None 0 +001200:00135C Other None 0 +00135D:00135F Extend Extend 0 +001360:001711 Other None 0 +001712:001715 Extend Extend 0 +001716:001731 Other None 0 +001732:001734 Extend Extend 0 +001735:001751 Other None 0 +001752:001753 Extend Extend 0 +001754:001771 Other None 0 +001772:001773 Extend Extend 0 +001774:00177F Other None 0 +001780:0017B3 Other Consonant 0 +0017B4:0017B5 Extend Extend 0 +0017B6:0017B6 SpacingMark None 0 +0017B7:0017BD Extend Extend 0 +0017BE:0017C5 SpacingMark None 0 +0017C6:0017C6 Extend Extend 0 +0017C7:0017C8 SpacingMark None 0 +0017C9:0017D1 Extend Extend 0 +0017D2:0017D2 Extend Linker 0 +0017D3:0017D3 Extend Extend 0 +0017D4:0017DC Other None 0 +0017DD:0017DD Extend Extend 0 +0017DE:00180A Other None 0 +00180B:00180D Extend Extend 0 +00180E:00180E Control None 0 +00180F:00180F Extend Extend 0 +001810:001884 Other None 0 +001885:001886 Extend Extend 0 +001887:0018A8 Other None 0 +0018A9:0018A9 Extend Extend 0 +0018AA:00191F Other None 0 +001920:001922 Extend Extend 0 +001923:001926 SpacingMark None 0 +001927:001928 Extend Extend 0 +001929:00192B SpacingMark None 0 +00192C:00192F Other None 0 +001930:001931 SpacingMark None 0 +001932:001932 Extend Extend 0 +001933:001938 SpacingMark None 0 +001939:00193B Extend Extend 0 +00193C:001A16 Other None 0 +001A17:001A18 Extend Extend 0 +001A19:001A1A SpacingMark None 0 +001A1B:001A1B Extend Extend 0 +001A1C:001A1F Other None 0 +001A20:001A54 Other Consonant 0 +001A55:001A55 SpacingMark None 0 +001A56:001A56 Extend Extend 0 +001A57:001A57 SpacingMark None 0 +001A58:001A5E Extend Extend 0 +001A5F:001A5F Other None 0 +001A60:001A60 Extend Linker 0 +001A61:001A61 Other None 0 +001A62:001A62 Extend Extend 0 +001A63:001A64 Other None 0 +001A65:001A6C Extend Extend 0 +001A6D:001A72 SpacingMark None 0 +001A73:001A7C Extend Extend 0 +001A7D:001A7E Other None 0 +001A7F:001A7F Extend Extend 0 +001A80:001AAF Other None 0 +001AB0:001ADD Extend Extend 0 +001ADE:001ADF Other None 0 +001AE0:001AEB Extend Extend 0 +001AEC:001AFF Other None 0 +001B00:001B03 Extend Extend 0 +001B04:001B04 SpacingMark None 0 +001B05:001B0A Other None 0 +001B0B:001B0C Other Consonant 0 +001B0D:001B12 Other None 0 +001B13:001B33 Other Consonant 0 +001B34:001B3D Extend Extend 0 +001B3E:001B41 SpacingMark None 0 +001B42:001B43 Extend Extend 0 +001B44:001B44 Extend Linker 0 +001B45:001B4C Other Consonant 0 +001B4D:001B6A Other None 0 +001B6B:001B73 Extend Extend 0 +001B74:001B7F Other None 0 +001B80:001B81 Extend Extend 0 +001B82:001B82 SpacingMark None 0 +001B83:001BA0 Other Consonant 0 +001BA1:001BA1 SpacingMark None 0 +001BA2:001BA5 Extend Extend 0 +001BA6:001BA7 SpacingMark None 0 +001BA8:001BAA Extend Extend 0 +001BAB:001BAB Extend Linker 0 +001BAC:001BAD Extend Extend 0 +001BAE:001BAF Other Consonant 0 +001BB0:001BBA Other None 0 +001BBB:001BBD Other Consonant 0 +001BBE:001BE5 Other None 0 +001BE6:001BE6 Extend Extend 0 +001BE7:001BE7 SpacingMark None 0 +001BE8:001BE9 Extend Extend 0 +001BEA:001BEC SpacingMark None 0 +001BED:001BED Extend Extend 0 +001BEE:001BEE SpacingMark None 0 +001BEF:001BF3 Extend Extend 0 +001BF4:001C23 Other None 0 +001C24:001C2B SpacingMark None 0 +001C2C:001C33 Extend Extend 0 +001C34:001C35 SpacingMark None 0 +001C36:001C37 Extend Extend 0 +001C38:001CCF Other None 0 +001CD0:001CD2 Extend Extend 0 +001CD3:001CD3 Other None 0 +001CD4:001CE0 Extend Extend 0 +001CE1:001CE1 SpacingMark None 0 +001CE2:001CE8 Extend Extend 0 +001CE9:001CEC Other None 0 +001CED:001CED Extend Extend 0 +001CEE:001CF3 Other None 0 +001CF4:001CF4 Extend Extend 0 +001CF5:001CF6 Other None 0 +001CF7:001CF7 SpacingMark None 0 +001CF8:001CF9 Extend Extend 0 +001CFA:001DBF Other None 0 +001DC0:001DFF Extend Extend 0 +001E00:00200A Other None 0 +00200B:00200B Control None 0 +00200C:00200C Extend None 0 +00200D:00200D ZWJ Extend 0 +00200E:00200F Control None 0 +002010:002027 Other None 0 +002028:00202E Control None 0 +00202F:00203B Other None 0 +00203C:00203C Other None 1 +00203D:002048 Other None 0 +002049:002049 Other None 1 +00204A:00205F Other None 0 +002060:00206F Control None 0 +002070:0020CF Other None 0 +0020D0:0020F0 Extend Extend 0 +0020F1:002121 Other None 0 +002122:002122 Other None 1 +002123:002138 Other None 0 +002139:002139 Other None 1 +00213A:002193 Other None 0 +002194:002199 Other None 1 +00219A:0021A8 Other None 0 +0021A9:0021AA Other None 1 +0021AB:002319 Other None 0 +00231A:00231B Other None 1 +00231C:002327 Other None 0 +002328:002328 Other None 1 +002329:0023CE Other None 0 +0023CF:0023CF Other None 1 +0023D0:0023E8 Other None 0 +0023E9:0023F3 Other None 1 +0023F4:0023F7 Other None 0 +0023F8:0023FA Other None 1 +0023FB:0024C1 Other None 0 +0024C2:0024C2 Other None 1 +0024C3:0025A9 Other None 0 +0025AA:0025AB Other None 1 +0025AC:0025B5 Other None 0 +0025B6:0025B6 Other None 1 +0025B7:0025BF Other None 0 +0025C0:0025C0 Other None 1 +0025C1:0025FA Other None 0 +0025FB:0025FE Other None 1 +0025FF:0025FF Other None 0 +002600:002604 Other None 1 +002605:00260D Other None 0 +00260E:00260E Other None 1 +00260F:002610 Other None 0 +002611:002611 Other None 1 +002612:002613 Other None 0 +002614:002615 Other None 1 +002616:002617 Other None 0 +002618:002618 Other None 1 +002619:00261C Other None 0 +00261D:00261D Other None 1 +00261E:00261F Other None 0 +002620:002620 Other None 1 +002621:002621 Other None 0 +002622:002623 Other None 1 +002624:002625 Other None 0 +002626:002626 Other None 1 +002627:002629 Other None 0 +00262A:00262A Other None 1 +00262B:00262D Other None 0 +00262E:00262F Other None 1 +002630:002637 Other None 0 +002638:00263A Other None 1 +00263B:00263F Other None 0 +002640:002640 Other None 1 +002641:002641 Other None 0 +002642:002642 Other None 1 +002643:002647 Other None 0 +002648:002653 Other None 1 +002654:00265E Other None 0 +00265F:002660 Other None 1 +002661:002662 Other None 0 +002663:002663 Other None 1 +002664:002664 Other None 0 +002665:002666 Other None 1 +002667:002667 Other None 0 +002668:002668 Other None 1 +002669:00267A Other None 0 +00267B:00267B Other None 1 +00267C:00267D Other None 0 +00267E:00267F Other None 1 +002680:002691 Other None 0 +002692:002697 Other None 1 +002698:002698 Other None 0 +002699:002699 Other None 1 +00269A:00269A Other None 0 +00269B:00269C Other None 1 +00269D:00269F Other None 0 +0026A0:0026A1 Other None 1 +0026A2:0026A6 Other None 0 +0026A7:0026A7 Other None 1 +0026A8:0026A9 Other None 0 +0026AA:0026AB Other None 1 +0026AC:0026AF Other None 0 +0026B0:0026B1 Other None 1 +0026B2:0026BC Other None 0 +0026BD:0026BE Other None 1 +0026BF:0026C3 Other None 0 +0026C4:0026C5 Other None 1 +0026C6:0026C7 Other None 0 +0026C8:0026C8 Other None 1 +0026C9:0026CD Other None 0 +0026CE:0026CF Other None 1 +0026D0:0026D0 Other None 0 +0026D1:0026D1 Other None 1 +0026D2:0026D2 Other None 0 +0026D3:0026D4 Other None 1 +0026D5:0026E8 Other None 0 +0026E9:0026EA Other None 1 +0026EB:0026EF Other None 0 +0026F0:0026F5 Other None 1 +0026F6:0026F6 Other None 0 +0026F7:0026FA Other None 1 +0026FB:0026FC Other None 0 +0026FD:0026FD Other None 1 +0026FE:002701 Other None 0 +002702:002702 Other None 1 +002703:002704 Other None 0 +002705:002705 Other None 1 +002706:002707 Other None 0 +002708:00270D Other None 1 +00270E:00270E Other None 0 +00270F:00270F Other None 1 +002710:002711 Other None 0 +002712:002712 Other None 1 +002713:002713 Other None 0 +002714:002714 Other None 1 +002715:002715 Other None 0 +002716:002716 Other None 1 +002717:00271C Other None 0 +00271D:00271D Other None 1 +00271E:002720 Other None 0 +002721:002721 Other None 1 +002722:002727 Other None 0 +002728:002728 Other None 1 +002729:002732 Other None 0 +002733:002734 Other None 1 +002735:002743 Other None 0 +002744:002744 Other None 1 +002745:002746 Other None 0 +002747:002747 Other None 1 +002748:00274B Other None 0 +00274C:00274C Other None 1 +00274D:00274D Other None 0 +00274E:00274E Other None 1 +00274F:002752 Other None 0 +002753:002755 Other None 1 +002756:002756 Other None 0 +002757:002757 Other None 1 +002758:002762 Other None 0 +002763:002764 Other None 1 +002765:002794 Other None 0 +002795:002797 Other None 1 +002798:0027A0 Other None 0 +0027A1:0027A1 Other None 1 +0027A2:0027AF Other None 0 +0027B0:0027B0 Other None 1 +0027B1:0027BE Other None 0 +0027BF:0027BF Other None 1 +0027C0:002933 Other None 0 +002934:002935 Other None 1 +002936:002B04 Other None 0 +002B05:002B07 Other None 1 +002B08:002B1A Other None 0 +002B1B:002B1C Other None 1 +002B1D:002B4F Other None 0 +002B50:002B50 Other None 1 +002B51:002B54 Other None 0 +002B55:002B55 Other None 1 +002B56:002CEE Other None 0 +002CEF:002CF1 Extend Extend 0 +002CF2:002D7E Other None 0 +002D7F:002D7F Extend Extend 0 +002D80:002DDF Other None 0 +002DE0:002DFF Extend Extend 0 +002E00:003029 Other None 0 +00302A:00302F Extend Extend 0 +003030:003030 Other None 1 +003031:00303C Other None 0 +00303D:00303D Other None 1 +00303E:003098 Other None 0 +003099:00309A Extend Extend 0 +00309B:003296 Other None 0 +003297:003297 Other None 1 +003298:003298 Other None 0 +003299:003299 Other None 1 +00329A:00A66E Other None 0 +00A66F:00A672 Extend Extend 0 +00A673:00A673 Other None 0 +00A674:00A67D Extend Extend 0 +00A67E:00A69D Other None 0 +00A69E:00A69F Extend Extend 0 +00A6A0:00A6EF Other None 0 +00A6F0:00A6F1 Extend Extend 0 +00A6F2:00A801 Other None 0 +00A802:00A802 Extend Extend 0 +00A803:00A805 Other None 0 +00A806:00A806 Extend Extend 0 +00A807:00A80A Other None 0 +00A80B:00A80B Extend Extend 0 +00A80C:00A822 Other None 0 +00A823:00A824 SpacingMark None 0 +00A825:00A826 Extend Extend 0 +00A827:00A827 SpacingMark None 0 +00A828:00A82B Other None 0 +00A82C:00A82C Extend Extend 0 +00A82D:00A87F Other None 0 +00A880:00A881 SpacingMark None 0 +00A882:00A8B3 Other None 0 +00A8B4:00A8C3 SpacingMark None 0 +00A8C4:00A8C5 Extend Extend 0 +00A8C6:00A8DF Other None 0 +00A8E0:00A8F1 Extend Extend 0 +00A8F2:00A8FE Other None 0 +00A8FF:00A8FF Extend Extend 0 +00A900:00A925 Other None 0 +00A926:00A92D Extend Extend 0 +00A92E:00A946 Other None 0 +00A947:00A951 Extend Extend 0 +00A952:00A952 SpacingMark None 0 +00A953:00A953 Extend Extend 0 +00A954:00A95F Other None 0 +00A960:00A97C L None 0 +00A97D:00A97F Other None 0 +00A980:00A982 Extend Extend 0 +00A983:00A983 SpacingMark None 0 +00A984:00A988 Other None 0 +00A989:00A98B Other Consonant 0 +00A98C:00A98E Other None 0 +00A98F:00A9B2 Other Consonant 0 +00A9B3:00A9B3 Extend Extend 0 +00A9B4:00A9B5 SpacingMark None 0 +00A9B6:00A9B9 Extend Extend 0 +00A9BA:00A9BB SpacingMark None 0 +00A9BC:00A9BD Extend Extend 0 +00A9BE:00A9BF SpacingMark None 0 +00A9C0:00A9C0 Extend Linker 0 +00A9C1:00A9DF Other None 0 +00A9E0:00A9E4 Other Consonant 0 +00A9E5:00A9E5 Extend Extend 0 +00A9E6:00A9E6 Other None 0 +00A9E7:00A9EF Other Consonant 0 +00A9F0:00A9F9 Other None 0 +00A9FA:00A9FE Other Consonant 0 +00A9FF:00AA28 Other None 0 +00AA29:00AA2E Extend Extend 0 +00AA2F:00AA30 SpacingMark None 0 +00AA31:00AA32 Extend Extend 0 +00AA33:00AA34 SpacingMark None 0 +00AA35:00AA36 Extend Extend 0 +00AA37:00AA42 Other None 0 +00AA43:00AA43 Extend Extend 0 +00AA44:00AA4B Other None 0 +00AA4C:00AA4C Extend Extend 0 +00AA4D:00AA4D SpacingMark None 0 +00AA4E:00AA5F Other None 0 +00AA60:00AA6F Other Consonant 0 +00AA70:00AA70 Other None 0 +00AA71:00AA73 Other Consonant 0 +00AA74:00AA79 Other None 0 +00AA7A:00AA7A Other Consonant 0 +00AA7B:00AA7B Other None 0 +00AA7C:00AA7C Extend Extend 0 +00AA7D:00AA7D Other None 0 +00AA7E:00AA7F Other Consonant 0 +00AA80:00AAAF Other None 0 +00AAB0:00AAB0 Extend Extend 0 +00AAB1:00AAB1 Other None 0 +00AAB2:00AAB4 Extend Extend 0 +00AAB5:00AAB6 Other None 0 +00AAB7:00AAB8 Extend Extend 0 +00AAB9:00AABD Other None 0 +00AABE:00AABF Extend Extend 0 +00AAC0:00AAC0 Other None 0 +00AAC1:00AAC1 Extend Extend 0 +00AAC2:00AADF Other None 0 +00AAE0:00AAEA Other Consonant 0 +00AAEB:00AAEB SpacingMark None 0 +00AAEC:00AAED Extend Extend 0 +00AAEE:00AAEF SpacingMark None 0 +00AAF0:00AAF4 Other None 0 +00AAF5:00AAF5 SpacingMark None 0 +00AAF6:00AAF6 Extend Linker 0 +00AAF7:00ABBF Other None 0 +00ABC0:00ABDA Other Consonant 0 +00ABDB:00ABE2 Other None 0 +00ABE3:00ABE4 SpacingMark None 0 +00ABE5:00ABE5 Extend Extend 0 +00ABE6:00ABE7 SpacingMark None 0 +00ABE8:00ABE8 Extend Extend 0 +00ABE9:00ABEA SpacingMark None 0 +00ABEB:00ABEB Other None 0 +00ABEC:00ABEC SpacingMark None 0 +00ABED:00ABED Extend Extend 0 +00ABEE:00ABFF Other None 0 +00AC00:00AC00 LV None 0 +00AC01:00AC1B LVT None 0 +00AC1C:00AC1C LV None 0 +00AC1D:00AC37 LVT None 0 +00AC38:00AC38 LV None 0 +00AC39:00AC53 LVT None 0 +00AC54:00AC54 LV None 0 +00AC55:00AC6F LVT None 0 +00AC70:00AC70 LV None 0 +00AC71:00AC8B LVT None 0 +00AC8C:00AC8C LV None 0 +00AC8D:00ACA7 LVT None 0 +00ACA8:00ACA8 LV None 0 +00ACA9:00ACC3 LVT None 0 +00ACC4:00ACC4 LV None 0 +00ACC5:00ACDF LVT None 0 +00ACE0:00ACE0 LV None 0 +00ACE1:00ACFB LVT None 0 +00ACFC:00ACFC LV None 0 +00ACFD:00AD17 LVT None 0 +00AD18:00AD18 LV None 0 +00AD19:00AD33 LVT None 0 +00AD34:00AD34 LV None 0 +00AD35:00AD4F LVT None 0 +00AD50:00AD50 LV None 0 +00AD51:00AD6B LVT None 0 +00AD6C:00AD6C LV None 0 +00AD6D:00AD87 LVT None 0 +00AD88:00AD88 LV None 0 +00AD89:00ADA3 LVT None 0 +00ADA4:00ADA4 LV None 0 +00ADA5:00ADBF LVT None 0 +00ADC0:00ADC0 LV None 0 +00ADC1:00ADDB LVT None 0 +00ADDC:00ADDC LV None 0 +00ADDD:00ADF7 LVT None 0 +00ADF8:00ADF8 LV None 0 +00ADF9:00AE13 LVT None 0 +00AE14:00AE14 LV None 0 +00AE15:00AE2F LVT None 0 +00AE30:00AE30 LV None 0 +00AE31:00AE4B LVT None 0 +00AE4C:00AE4C LV None 0 +00AE4D:00AE67 LVT None 0 +00AE68:00AE68 LV None 0 +00AE69:00AE83 LVT None 0 +00AE84:00AE84 LV None 0 +00AE85:00AE9F LVT None 0 +00AEA0:00AEA0 LV None 0 +00AEA1:00AEBB LVT None 0 +00AEBC:00AEBC LV None 0 +00AEBD:00AED7 LVT None 0 +00AED8:00AED8 LV None 0 +00AED9:00AEF3 LVT None 0 +00AEF4:00AEF4 LV None 0 +00AEF5:00AF0F LVT None 0 +00AF10:00AF10 LV None 0 +00AF11:00AF2B LVT None 0 +00AF2C:00AF2C LV None 0 +00AF2D:00AF47 LVT None 0 +00AF48:00AF48 LV None 0 +00AF49:00AF63 LVT None 0 +00AF64:00AF64 LV None 0 +00AF65:00AF7F LVT None 0 +00AF80:00AF80 LV None 0 +00AF81:00AF9B LVT None 0 +00AF9C:00AF9C LV None 0 +00AF9D:00AFB7 LVT None 0 +00AFB8:00AFB8 LV None 0 +00AFB9:00AFD3 LVT None 0 +00AFD4:00AFD4 LV None 0 +00AFD5:00AFEF LVT None 0 +00AFF0:00AFF0 LV None 0 +00AFF1:00B00B LVT None 0 +00B00C:00B00C LV None 0 +00B00D:00B027 LVT None 0 +00B028:00B028 LV None 0 +00B029:00B043 LVT None 0 +00B044:00B044 LV None 0 +00B045:00B05F LVT None 0 +00B060:00B060 LV None 0 +00B061:00B07B LVT None 0 +00B07C:00B07C LV None 0 +00B07D:00B097 LVT None 0 +00B098:00B098 LV None 0 +00B099:00B0B3 LVT None 0 +00B0B4:00B0B4 LV None 0 +00B0B5:00B0CF LVT None 0 +00B0D0:00B0D0 LV None 0 +00B0D1:00B0EB LVT None 0 +00B0EC:00B0EC LV None 0 +00B0ED:00B107 LVT None 0 +00B108:00B108 LV None 0 +00B109:00B123 LVT None 0 +00B124:00B124 LV None 0 +00B125:00B13F LVT None 0 +00B140:00B140 LV None 0 +00B141:00B15B LVT None 0 +00B15C:00B15C LV None 0 +00B15D:00B177 LVT None 0 +00B178:00B178 LV None 0 +00B179:00B193 LVT None 0 +00B194:00B194 LV None 0 +00B195:00B1AF LVT None 0 +00B1B0:00B1B0 LV None 0 +00B1B1:00B1CB LVT None 0 +00B1CC:00B1CC LV None 0 +00B1CD:00B1E7 LVT None 0 +00B1E8:00B1E8 LV None 0 +00B1E9:00B203 LVT None 0 +00B204:00B204 LV None 0 +00B205:00B21F LVT None 0 +00B220:00B220 LV None 0 +00B221:00B23B LVT None 0 +00B23C:00B23C LV None 0 +00B23D:00B257 LVT None 0 +00B258:00B258 LV None 0 +00B259:00B273 LVT None 0 +00B274:00B274 LV None 0 +00B275:00B28F LVT None 0 +00B290:00B290 LV None 0 +00B291:00B2AB LVT None 0 +00B2AC:00B2AC LV None 0 +00B2AD:00B2C7 LVT None 0 +00B2C8:00B2C8 LV None 0 +00B2C9:00B2E3 LVT None 0 +00B2E4:00B2E4 LV None 0 +00B2E5:00B2FF LVT None 0 +00B300:00B300 LV None 0 +00B301:00B31B LVT None 0 +00B31C:00B31C LV None 0 +00B31D:00B337 LVT None 0 +00B338:00B338 LV None 0 +00B339:00B353 LVT None 0 +00B354:00B354 LV None 0 +00B355:00B36F LVT None 0 +00B370:00B370 LV None 0 +00B371:00B38B LVT None 0 +00B38C:00B38C LV None 0 +00B38D:00B3A7 LVT None 0 +00B3A8:00B3A8 LV None 0 +00B3A9:00B3C3 LVT None 0 +00B3C4:00B3C4 LV None 0 +00B3C5:00B3DF LVT None 0 +00B3E0:00B3E0 LV None 0 +00B3E1:00B3FB LVT None 0 +00B3FC:00B3FC LV None 0 +00B3FD:00B417 LVT None 0 +00B418:00B418 LV None 0 +00B419:00B433 LVT None 0 +00B434:00B434 LV None 0 +00B435:00B44F LVT None 0 +00B450:00B450 LV None 0 +00B451:00B46B LVT None 0 +00B46C:00B46C LV None 0 +00B46D:00B487 LVT None 0 +00B488:00B488 LV None 0 +00B489:00B4A3 LVT None 0 +00B4A4:00B4A4 LV None 0 +00B4A5:00B4BF LVT None 0 +00B4C0:00B4C0 LV None 0 +00B4C1:00B4DB LVT None 0 +00B4DC:00B4DC LV None 0 +00B4DD:00B4F7 LVT None 0 +00B4F8:00B4F8 LV None 0 +00B4F9:00B513 LVT None 0 +00B514:00B514 LV None 0 +00B515:00B52F LVT None 0 +00B530:00B530 LV None 0 +00B531:00B54B LVT None 0 +00B54C:00B54C LV None 0 +00B54D:00B567 LVT None 0 +00B568:00B568 LV None 0 +00B569:00B583 LVT None 0 +00B584:00B584 LV None 0 +00B585:00B59F LVT None 0 +00B5A0:00B5A0 LV None 0 +00B5A1:00B5BB LVT None 0 +00B5BC:00B5BC LV None 0 +00B5BD:00B5D7 LVT None 0 +00B5D8:00B5D8 LV None 0 +00B5D9:00B5F3 LVT None 0 +00B5F4:00B5F4 LV None 0 +00B5F5:00B60F LVT None 0 +00B610:00B610 LV None 0 +00B611:00B62B LVT None 0 +00B62C:00B62C LV None 0 +00B62D:00B647 LVT None 0 +00B648:00B648 LV None 0 +00B649:00B663 LVT None 0 +00B664:00B664 LV None 0 +00B665:00B67F LVT None 0 +00B680:00B680 LV None 0 +00B681:00B69B LVT None 0 +00B69C:00B69C LV None 0 +00B69D:00B6B7 LVT None 0 +00B6B8:00B6B8 LV None 0 +00B6B9:00B6D3 LVT None 0 +00B6D4:00B6D4 LV None 0 +00B6D5:00B6EF LVT None 0 +00B6F0:00B6F0 LV None 0 +00B6F1:00B70B LVT None 0 +00B70C:00B70C LV None 0 +00B70D:00B727 LVT None 0 +00B728:00B728 LV None 0 +00B729:00B743 LVT None 0 +00B744:00B744 LV None 0 +00B745:00B75F LVT None 0 +00B760:00B760 LV None 0 +00B761:00B77B LVT None 0 +00B77C:00B77C LV None 0 +00B77D:00B797 LVT None 0 +00B798:00B798 LV None 0 +00B799:00B7B3 LVT None 0 +00B7B4:00B7B4 LV None 0 +00B7B5:00B7CF LVT None 0 +00B7D0:00B7D0 LV None 0 +00B7D1:00B7EB LVT None 0 +00B7EC:00B7EC LV None 0 +00B7ED:00B807 LVT None 0 +00B808:00B808 LV None 0 +00B809:00B823 LVT None 0 +00B824:00B824 LV None 0 +00B825:00B83F LVT None 0 +00B840:00B840 LV None 0 +00B841:00B85B LVT None 0 +00B85C:00B85C LV None 0 +00B85D:00B877 LVT None 0 +00B878:00B878 LV None 0 +00B879:00B893 LVT None 0 +00B894:00B894 LV None 0 +00B895:00B8AF LVT None 0 +00B8B0:00B8B0 LV None 0 +00B8B1:00B8CB LVT None 0 +00B8CC:00B8CC LV None 0 +00B8CD:00B8E7 LVT None 0 +00B8E8:00B8E8 LV None 0 +00B8E9:00B903 LVT None 0 +00B904:00B904 LV None 0 +00B905:00B91F LVT None 0 +00B920:00B920 LV None 0 +00B921:00B93B LVT None 0 +00B93C:00B93C LV None 0 +00B93D:00B957 LVT None 0 +00B958:00B958 LV None 0 +00B959:00B973 LVT None 0 +00B974:00B974 LV None 0 +00B975:00B98F LVT None 0 +00B990:00B990 LV None 0 +00B991:00B9AB LVT None 0 +00B9AC:00B9AC LV None 0 +00B9AD:00B9C7 LVT None 0 +00B9C8:00B9C8 LV None 0 +00B9C9:00B9E3 LVT None 0 +00B9E4:00B9E4 LV None 0 +00B9E5:00B9FF LVT None 0 +00BA00:00BA00 LV None 0 +00BA01:00BA1B LVT None 0 +00BA1C:00BA1C LV None 0 +00BA1D:00BA37 LVT None 0 +00BA38:00BA38 LV None 0 +00BA39:00BA53 LVT None 0 +00BA54:00BA54 LV None 0 +00BA55:00BA6F LVT None 0 +00BA70:00BA70 LV None 0 +00BA71:00BA8B LVT None 0 +00BA8C:00BA8C LV None 0 +00BA8D:00BAA7 LVT None 0 +00BAA8:00BAA8 LV None 0 +00BAA9:00BAC3 LVT None 0 +00BAC4:00BAC4 LV None 0 +00BAC5:00BADF LVT None 0 +00BAE0:00BAE0 LV None 0 +00BAE1:00BAFB LVT None 0 +00BAFC:00BAFC LV None 0 +00BAFD:00BB17 LVT None 0 +00BB18:00BB18 LV None 0 +00BB19:00BB33 LVT None 0 +00BB34:00BB34 LV None 0 +00BB35:00BB4F LVT None 0 +00BB50:00BB50 LV None 0 +00BB51:00BB6B LVT None 0 +00BB6C:00BB6C LV None 0 +00BB6D:00BB87 LVT None 0 +00BB88:00BB88 LV None 0 +00BB89:00BBA3 LVT None 0 +00BBA4:00BBA4 LV None 0 +00BBA5:00BBBF LVT None 0 +00BBC0:00BBC0 LV None 0 +00BBC1:00BBDB LVT None 0 +00BBDC:00BBDC LV None 0 +00BBDD:00BBF7 LVT None 0 +00BBF8:00BBF8 LV None 0 +00BBF9:00BC13 LVT None 0 +00BC14:00BC14 LV None 0 +00BC15:00BC2F LVT None 0 +00BC30:00BC30 LV None 0 +00BC31:00BC4B LVT None 0 +00BC4C:00BC4C LV None 0 +00BC4D:00BC67 LVT None 0 +00BC68:00BC68 LV None 0 +00BC69:00BC83 LVT None 0 +00BC84:00BC84 LV None 0 +00BC85:00BC9F LVT None 0 +00BCA0:00BCA0 LV None 0 +00BCA1:00BCBB LVT None 0 +00BCBC:00BCBC LV None 0 +00BCBD:00BCD7 LVT None 0 +00BCD8:00BCD8 LV None 0 +00BCD9:00BCF3 LVT None 0 +00BCF4:00BCF4 LV None 0 +00BCF5:00BD0F LVT None 0 +00BD10:00BD10 LV None 0 +00BD11:00BD2B LVT None 0 +00BD2C:00BD2C LV None 0 +00BD2D:00BD47 LVT None 0 +00BD48:00BD48 LV None 0 +00BD49:00BD63 LVT None 0 +00BD64:00BD64 LV None 0 +00BD65:00BD7F LVT None 0 +00BD80:00BD80 LV None 0 +00BD81:00BD9B LVT None 0 +00BD9C:00BD9C LV None 0 +00BD9D:00BDB7 LVT None 0 +00BDB8:00BDB8 LV None 0 +00BDB9:00BDD3 LVT None 0 +00BDD4:00BDD4 LV None 0 +00BDD5:00BDEF LVT None 0 +00BDF0:00BDF0 LV None 0 +00BDF1:00BE0B LVT None 0 +00BE0C:00BE0C LV None 0 +00BE0D:00BE27 LVT None 0 +00BE28:00BE28 LV None 0 +00BE29:00BE43 LVT None 0 +00BE44:00BE44 LV None 0 +00BE45:00BE5F LVT None 0 +00BE60:00BE60 LV None 0 +00BE61:00BE7B LVT None 0 +00BE7C:00BE7C LV None 0 +00BE7D:00BE97 LVT None 0 +00BE98:00BE98 LV None 0 +00BE99:00BEB3 LVT None 0 +00BEB4:00BEB4 LV None 0 +00BEB5:00BECF LVT None 0 +00BED0:00BED0 LV None 0 +00BED1:00BEEB LVT None 0 +00BEEC:00BEEC LV None 0 +00BEED:00BF07 LVT None 0 +00BF08:00BF08 LV None 0 +00BF09:00BF23 LVT None 0 +00BF24:00BF24 LV None 0 +00BF25:00BF3F LVT None 0 +00BF40:00BF40 LV None 0 +00BF41:00BF5B LVT None 0 +00BF5C:00BF5C LV None 0 +00BF5D:00BF77 LVT None 0 +00BF78:00BF78 LV None 0 +00BF79:00BF93 LVT None 0 +00BF94:00BF94 LV None 0 +00BF95:00BFAF LVT None 0 +00BFB0:00BFB0 LV None 0 +00BFB1:00BFCB LVT None 0 +00BFCC:00BFCC LV None 0 +00BFCD:00BFE7 LVT None 0 +00BFE8:00BFE8 LV None 0 +00BFE9:00C003 LVT None 0 +00C004:00C004 LV None 0 +00C005:00C01F LVT None 0 +00C020:00C020 LV None 0 +00C021:00C03B LVT None 0 +00C03C:00C03C LV None 0 +00C03D:00C057 LVT None 0 +00C058:00C058 LV None 0 +00C059:00C073 LVT None 0 +00C074:00C074 LV None 0 +00C075:00C08F LVT None 0 +00C090:00C090 LV None 0 +00C091:00C0AB LVT None 0 +00C0AC:00C0AC LV None 0 +00C0AD:00C0C7 LVT None 0 +00C0C8:00C0C8 LV None 0 +00C0C9:00C0E3 LVT None 0 +00C0E4:00C0E4 LV None 0 +00C0E5:00C0FF LVT None 0 +00C100:00C100 LV None 0 +00C101:00C11B LVT None 0 +00C11C:00C11C LV None 0 +00C11D:00C137 LVT None 0 +00C138:00C138 LV None 0 +00C139:00C153 LVT None 0 +00C154:00C154 LV None 0 +00C155:00C16F LVT None 0 +00C170:00C170 LV None 0 +00C171:00C18B LVT None 0 +00C18C:00C18C LV None 0 +00C18D:00C1A7 LVT None 0 +00C1A8:00C1A8 LV None 0 +00C1A9:00C1C3 LVT None 0 +00C1C4:00C1C4 LV None 0 +00C1C5:00C1DF LVT None 0 +00C1E0:00C1E0 LV None 0 +00C1E1:00C1FB LVT None 0 +00C1FC:00C1FC LV None 0 +00C1FD:00C217 LVT None 0 +00C218:00C218 LV None 0 +00C219:00C233 LVT None 0 +00C234:00C234 LV None 0 +00C235:00C24F LVT None 0 +00C250:00C250 LV None 0 +00C251:00C26B LVT None 0 +00C26C:00C26C LV None 0 +00C26D:00C287 LVT None 0 +00C288:00C288 LV None 0 +00C289:00C2A3 LVT None 0 +00C2A4:00C2A4 LV None 0 +00C2A5:00C2BF LVT None 0 +00C2C0:00C2C0 LV None 0 +00C2C1:00C2DB LVT None 0 +00C2DC:00C2DC LV None 0 +00C2DD:00C2F7 LVT None 0 +00C2F8:00C2F8 LV None 0 +00C2F9:00C313 LVT None 0 +00C314:00C314 LV None 0 +00C315:00C32F LVT None 0 +00C330:00C330 LV None 0 +00C331:00C34B LVT None 0 +00C34C:00C34C LV None 0 +00C34D:00C367 LVT None 0 +00C368:00C368 LV None 0 +00C369:00C383 LVT None 0 +00C384:00C384 LV None 0 +00C385:00C39F LVT None 0 +00C3A0:00C3A0 LV None 0 +00C3A1:00C3BB LVT None 0 +00C3BC:00C3BC LV None 0 +00C3BD:00C3D7 LVT None 0 +00C3D8:00C3D8 LV None 0 +00C3D9:00C3F3 LVT None 0 +00C3F4:00C3F4 LV None 0 +00C3F5:00C40F LVT None 0 +00C410:00C410 LV None 0 +00C411:00C42B LVT None 0 +00C42C:00C42C LV None 0 +00C42D:00C447 LVT None 0 +00C448:00C448 LV None 0 +00C449:00C463 LVT None 0 +00C464:00C464 LV None 0 +00C465:00C47F LVT None 0 +00C480:00C480 LV None 0 +00C481:00C49B LVT None 0 +00C49C:00C49C LV None 0 +00C49D:00C4B7 LVT None 0 +00C4B8:00C4B8 LV None 0 +00C4B9:00C4D3 LVT None 0 +00C4D4:00C4D4 LV None 0 +00C4D5:00C4EF LVT None 0 +00C4F0:00C4F0 LV None 0 +00C4F1:00C50B LVT None 0 +00C50C:00C50C LV None 0 +00C50D:00C527 LVT None 0 +00C528:00C528 LV None 0 +00C529:00C543 LVT None 0 +00C544:00C544 LV None 0 +00C545:00C55F LVT None 0 +00C560:00C560 LV None 0 +00C561:00C57B LVT None 0 +00C57C:00C57C LV None 0 +00C57D:00C597 LVT None 0 +00C598:00C598 LV None 0 +00C599:00C5B3 LVT None 0 +00C5B4:00C5B4 LV None 0 +00C5B5:00C5CF LVT None 0 +00C5D0:00C5D0 LV None 0 +00C5D1:00C5EB LVT None 0 +00C5EC:00C5EC LV None 0 +00C5ED:00C607 LVT None 0 +00C608:00C608 LV None 0 +00C609:00C623 LVT None 0 +00C624:00C624 LV None 0 +00C625:00C63F LVT None 0 +00C640:00C640 LV None 0 +00C641:00C65B LVT None 0 +00C65C:00C65C LV None 0 +00C65D:00C677 LVT None 0 +00C678:00C678 LV None 0 +00C679:00C693 LVT None 0 +00C694:00C694 LV None 0 +00C695:00C6AF LVT None 0 +00C6B0:00C6B0 LV None 0 +00C6B1:00C6CB LVT None 0 +00C6CC:00C6CC LV None 0 +00C6CD:00C6E7 LVT None 0 +00C6E8:00C6E8 LV None 0 +00C6E9:00C703 LVT None 0 +00C704:00C704 LV None 0 +00C705:00C71F LVT None 0 +00C720:00C720 LV None 0 +00C721:00C73B LVT None 0 +00C73C:00C73C LV None 0 +00C73D:00C757 LVT None 0 +00C758:00C758 LV None 0 +00C759:00C773 LVT None 0 +00C774:00C774 LV None 0 +00C775:00C78F LVT None 0 +00C790:00C790 LV None 0 +00C791:00C7AB LVT None 0 +00C7AC:00C7AC LV None 0 +00C7AD:00C7C7 LVT None 0 +00C7C8:00C7C8 LV None 0 +00C7C9:00C7E3 LVT None 0 +00C7E4:00C7E4 LV None 0 +00C7E5:00C7FF LVT None 0 +00C800:00C800 LV None 0 +00C801:00C81B LVT None 0 +00C81C:00C81C LV None 0 +00C81D:00C837 LVT None 0 +00C838:00C838 LV None 0 +00C839:00C853 LVT None 0 +00C854:00C854 LV None 0 +00C855:00C86F LVT None 0 +00C870:00C870 LV None 0 +00C871:00C88B LVT None 0 +00C88C:00C88C LV None 0 +00C88D:00C8A7 LVT None 0 +00C8A8:00C8A8 LV None 0 +00C8A9:00C8C3 LVT None 0 +00C8C4:00C8C4 LV None 0 +00C8C5:00C8DF LVT None 0 +00C8E0:00C8E0 LV None 0 +00C8E1:00C8FB LVT None 0 +00C8FC:00C8FC LV None 0 +00C8FD:00C917 LVT None 0 +00C918:00C918 LV None 0 +00C919:00C933 LVT None 0 +00C934:00C934 LV None 0 +00C935:00C94F LVT None 0 +00C950:00C950 LV None 0 +00C951:00C96B LVT None 0 +00C96C:00C96C LV None 0 +00C96D:00C987 LVT None 0 +00C988:00C988 LV None 0 +00C989:00C9A3 LVT None 0 +00C9A4:00C9A4 LV None 0 +00C9A5:00C9BF LVT None 0 +00C9C0:00C9C0 LV None 0 +00C9C1:00C9DB LVT None 0 +00C9DC:00C9DC LV None 0 +00C9DD:00C9F7 LVT None 0 +00C9F8:00C9F8 LV None 0 +00C9F9:00CA13 LVT None 0 +00CA14:00CA14 LV None 0 +00CA15:00CA2F LVT None 0 +00CA30:00CA30 LV None 0 +00CA31:00CA4B LVT None 0 +00CA4C:00CA4C LV None 0 +00CA4D:00CA67 LVT None 0 +00CA68:00CA68 LV None 0 +00CA69:00CA83 LVT None 0 +00CA84:00CA84 LV None 0 +00CA85:00CA9F LVT None 0 +00CAA0:00CAA0 LV None 0 +00CAA1:00CABB LVT None 0 +00CABC:00CABC LV None 0 +00CABD:00CAD7 LVT None 0 +00CAD8:00CAD8 LV None 0 +00CAD9:00CAF3 LVT None 0 +00CAF4:00CAF4 LV None 0 +00CAF5:00CB0F LVT None 0 +00CB10:00CB10 LV None 0 +00CB11:00CB2B LVT None 0 +00CB2C:00CB2C LV None 0 +00CB2D:00CB47 LVT None 0 +00CB48:00CB48 LV None 0 +00CB49:00CB63 LVT None 0 +00CB64:00CB64 LV None 0 +00CB65:00CB7F LVT None 0 +00CB80:00CB80 LV None 0 +00CB81:00CB9B LVT None 0 +00CB9C:00CB9C LV None 0 +00CB9D:00CBB7 LVT None 0 +00CBB8:00CBB8 LV None 0 +00CBB9:00CBD3 LVT None 0 +00CBD4:00CBD4 LV None 0 +00CBD5:00CBEF LVT None 0 +00CBF0:00CBF0 LV None 0 +00CBF1:00CC0B LVT None 0 +00CC0C:00CC0C LV None 0 +00CC0D:00CC27 LVT None 0 +00CC28:00CC28 LV None 0 +00CC29:00CC43 LVT None 0 +00CC44:00CC44 LV None 0 +00CC45:00CC5F LVT None 0 +00CC60:00CC60 LV None 0 +00CC61:00CC7B LVT None 0 +00CC7C:00CC7C LV None 0 +00CC7D:00CC97 LVT None 0 +00CC98:00CC98 LV None 0 +00CC99:00CCB3 LVT None 0 +00CCB4:00CCB4 LV None 0 +00CCB5:00CCCF LVT None 0 +00CCD0:00CCD0 LV None 0 +00CCD1:00CCEB LVT None 0 +00CCEC:00CCEC LV None 0 +00CCED:00CD07 LVT None 0 +00CD08:00CD08 LV None 0 +00CD09:00CD23 LVT None 0 +00CD24:00CD24 LV None 0 +00CD25:00CD3F LVT None 0 +00CD40:00CD40 LV None 0 +00CD41:00CD5B LVT None 0 +00CD5C:00CD5C LV None 0 +00CD5D:00CD77 LVT None 0 +00CD78:00CD78 LV None 0 +00CD79:00CD93 LVT None 0 +00CD94:00CD94 LV None 0 +00CD95:00CDAF LVT None 0 +00CDB0:00CDB0 LV None 0 +00CDB1:00CDCB LVT None 0 +00CDCC:00CDCC LV None 0 +00CDCD:00CDE7 LVT None 0 +00CDE8:00CDE8 LV None 0 +00CDE9:00CE03 LVT None 0 +00CE04:00CE04 LV None 0 +00CE05:00CE1F LVT None 0 +00CE20:00CE20 LV None 0 +00CE21:00CE3B LVT None 0 +00CE3C:00CE3C LV None 0 +00CE3D:00CE57 LVT None 0 +00CE58:00CE58 LV None 0 +00CE59:00CE73 LVT None 0 +00CE74:00CE74 LV None 0 +00CE75:00CE8F LVT None 0 +00CE90:00CE90 LV None 0 +00CE91:00CEAB LVT None 0 +00CEAC:00CEAC LV None 0 +00CEAD:00CEC7 LVT None 0 +00CEC8:00CEC8 LV None 0 +00CEC9:00CEE3 LVT None 0 +00CEE4:00CEE4 LV None 0 +00CEE5:00CEFF LVT None 0 +00CF00:00CF00 LV None 0 +00CF01:00CF1B LVT None 0 +00CF1C:00CF1C LV None 0 +00CF1D:00CF37 LVT None 0 +00CF38:00CF38 LV None 0 +00CF39:00CF53 LVT None 0 +00CF54:00CF54 LV None 0 +00CF55:00CF6F LVT None 0 +00CF70:00CF70 LV None 0 +00CF71:00CF8B LVT None 0 +00CF8C:00CF8C LV None 0 +00CF8D:00CFA7 LVT None 0 +00CFA8:00CFA8 LV None 0 +00CFA9:00CFC3 LVT None 0 +00CFC4:00CFC4 LV None 0 +00CFC5:00CFDF LVT None 0 +00CFE0:00CFE0 LV None 0 +00CFE1:00CFFB LVT None 0 +00CFFC:00CFFC LV None 0 +00CFFD:00D017 LVT None 0 +00D018:00D018 LV None 0 +00D019:00D033 LVT None 0 +00D034:00D034 LV None 0 +00D035:00D04F LVT None 0 +00D050:00D050 LV None 0 +00D051:00D06B LVT None 0 +00D06C:00D06C LV None 0 +00D06D:00D087 LVT None 0 +00D088:00D088 LV None 0 +00D089:00D0A3 LVT None 0 +00D0A4:00D0A4 LV None 0 +00D0A5:00D0BF LVT None 0 +00D0C0:00D0C0 LV None 0 +00D0C1:00D0DB LVT None 0 +00D0DC:00D0DC LV None 0 +00D0DD:00D0F7 LVT None 0 +00D0F8:00D0F8 LV None 0 +00D0F9:00D113 LVT None 0 +00D114:00D114 LV None 0 +00D115:00D12F LVT None 0 +00D130:00D130 LV None 0 +00D131:00D14B LVT None 0 +00D14C:00D14C LV None 0 +00D14D:00D167 LVT None 0 +00D168:00D168 LV None 0 +00D169:00D183 LVT None 0 +00D184:00D184 LV None 0 +00D185:00D19F LVT None 0 +00D1A0:00D1A0 LV None 0 +00D1A1:00D1BB LVT None 0 +00D1BC:00D1BC LV None 0 +00D1BD:00D1D7 LVT None 0 +00D1D8:00D1D8 LV None 0 +00D1D9:00D1F3 LVT None 0 +00D1F4:00D1F4 LV None 0 +00D1F5:00D20F LVT None 0 +00D210:00D210 LV None 0 +00D211:00D22B LVT None 0 +00D22C:00D22C LV None 0 +00D22D:00D247 LVT None 0 +00D248:00D248 LV None 0 +00D249:00D263 LVT None 0 +00D264:00D264 LV None 0 +00D265:00D27F LVT None 0 +00D280:00D280 LV None 0 +00D281:00D29B LVT None 0 +00D29C:00D29C LV None 0 +00D29D:00D2B7 LVT None 0 +00D2B8:00D2B8 LV None 0 +00D2B9:00D2D3 LVT None 0 +00D2D4:00D2D4 LV None 0 +00D2D5:00D2EF LVT None 0 +00D2F0:00D2F0 LV None 0 +00D2F1:00D30B LVT None 0 +00D30C:00D30C LV None 0 +00D30D:00D327 LVT None 0 +00D328:00D328 LV None 0 +00D329:00D343 LVT None 0 +00D344:00D344 LV None 0 +00D345:00D35F LVT None 0 +00D360:00D360 LV None 0 +00D361:00D37B LVT None 0 +00D37C:00D37C LV None 0 +00D37D:00D397 LVT None 0 +00D398:00D398 LV None 0 +00D399:00D3B3 LVT None 0 +00D3B4:00D3B4 LV None 0 +00D3B5:00D3CF LVT None 0 +00D3D0:00D3D0 LV None 0 +00D3D1:00D3EB LVT None 0 +00D3EC:00D3EC LV None 0 +00D3ED:00D407 LVT None 0 +00D408:00D408 LV None 0 +00D409:00D423 LVT None 0 +00D424:00D424 LV None 0 +00D425:00D43F LVT None 0 +00D440:00D440 LV None 0 +00D441:00D45B LVT None 0 +00D45C:00D45C LV None 0 +00D45D:00D477 LVT None 0 +00D478:00D478 LV None 0 +00D479:00D493 LVT None 0 +00D494:00D494 LV None 0 +00D495:00D4AF LVT None 0 +00D4B0:00D4B0 LV None 0 +00D4B1:00D4CB LVT None 0 +00D4CC:00D4CC LV None 0 +00D4CD:00D4E7 LVT None 0 +00D4E8:00D4E8 LV None 0 +00D4E9:00D503 LVT None 0 +00D504:00D504 LV None 0 +00D505:00D51F LVT None 0 +00D520:00D520 LV None 0 +00D521:00D53B LVT None 0 +00D53C:00D53C LV None 0 +00D53D:00D557 LVT None 0 +00D558:00D558 LV None 0 +00D559:00D573 LVT None 0 +00D574:00D574 LV None 0 +00D575:00D58F LVT None 0 +00D590:00D590 LV None 0 +00D591:00D5AB LVT None 0 +00D5AC:00D5AC LV None 0 +00D5AD:00D5C7 LVT None 0 +00D5C8:00D5C8 LV None 0 +00D5C9:00D5E3 LVT None 0 +00D5E4:00D5E4 LV None 0 +00D5E5:00D5FF LVT None 0 +00D600:00D600 LV None 0 +00D601:00D61B LVT None 0 +00D61C:00D61C LV None 0 +00D61D:00D637 LVT None 0 +00D638:00D638 LV None 0 +00D639:00D653 LVT None 0 +00D654:00D654 LV None 0 +00D655:00D66F LVT None 0 +00D670:00D670 LV None 0 +00D671:00D68B LVT None 0 +00D68C:00D68C LV None 0 +00D68D:00D6A7 LVT None 0 +00D6A8:00D6A8 LV None 0 +00D6A9:00D6C3 LVT None 0 +00D6C4:00D6C4 LV None 0 +00D6C5:00D6DF LVT None 0 +00D6E0:00D6E0 LV None 0 +00D6E1:00D6FB LVT None 0 +00D6FC:00D6FC LV None 0 +00D6FD:00D717 LVT None 0 +00D718:00D718 LV None 0 +00D719:00D733 LVT None 0 +00D734:00D734 LV None 0 +00D735:00D74F LVT None 0 +00D750:00D750 LV None 0 +00D751:00D76B LVT None 0 +00D76C:00D76C LV None 0 +00D76D:00D787 LVT None 0 +00D788:00D788 LV None 0 +00D789:00D7A3 LVT None 0 +00D7A4:00D7AF Other None 0 +00D7B0:00D7C6 V None 0 +00D7C7:00D7CA Other None 0 +00D7CB:00D7FB T None 0 +00D7FC:00FB1D Other None 0 +00FB1E:00FB1E Extend Extend 0 +00FB1F:00FDFF Other None 0 +00FE00:00FE0F Extend Extend 0 +00FE10:00FE1F Other None 0 +00FE20:00FE2F Extend Extend 0 +00FE30:00FEFE Other None 0 +00FEFF:00FEFF Control None 0 +00FF00:00FF9D Other None 0 +00FF9E:00FF9F Extend Extend 0 +00FFA0:00FFEF Other None 0 +00FFF0:00FFFB Control None 0 +00FFFC:0101FC Other None 0 +0101FD:0101FD Extend Extend 0 +0101FE:0102DF Other None 0 +0102E0:0102E0 Extend Extend 0 +0102E1:010375 Other None 0 +010376:01037A Extend Extend 0 +01037B:0109FF Other None 0 +010A00:010A00 Other Consonant 0 +010A01:010A03 Extend Extend 0 +010A04:010A04 Other None 0 +010A05:010A06 Extend Extend 0 +010A07:010A0B Other None 0 +010A0C:010A0F Extend Extend 0 +010A10:010A13 Other Consonant 0 +010A14:010A14 Other None 0 +010A15:010A17 Other Consonant 0 +010A18:010A18 Other None 0 +010A19:010A35 Other Consonant 0 +010A36:010A37 Other None 0 +010A38:010A3A Extend Extend 0 +010A3B:010A3E Other None 0 +010A3F:010A3F Extend Linker 0 +010A40:010AE4 Other None 0 +010AE5:010AE6 Extend Extend 0 +010AE7:010D23 Other None 0 +010D24:010D27 Extend Extend 0 +010D28:010D68 Other None 0 +010D69:010D6D Extend Extend 0 +010D6E:010EAA Other None 0 +010EAB:010EAC Extend Extend 0 +010EAD:010EF9 Other None 0 +010EFA:010EFF Extend Extend 0 +010F00:010F45 Other None 0 +010F46:010F50 Extend Extend 0 +010F51:010F81 Other None 0 +010F82:010F85 Extend Extend 0 +010F86:010FFF Other None 0 +011000:011000 SpacingMark None 0 +011001:011001 Extend Extend 0 +011002:011002 SpacingMark None 0 +011003:011037 Other None 0 +011038:011046 Extend Extend 0 +011047:01106F Other None 0 +011070:011070 Extend Extend 0 +011071:011072 Other None 0 +011073:011074 Extend Extend 0 +011075:01107E Other None 0 +01107F:011081 Extend Extend 0 +011082:011082 SpacingMark None 0 +011083:0110AF Other None 0 +0110B0:0110B2 SpacingMark None 0 +0110B3:0110B6 Extend Extend 0 +0110B7:0110B8 SpacingMark None 0 +0110B9:0110BA Extend Extend 0 +0110BB:0110BC Other None 0 +0110BD:0110BD Prepend None 0 +0110BE:0110C1 Other None 0 +0110C2:0110C2 Extend Extend 0 +0110C3:0110CC Other None 0 +0110CD:0110CD Prepend None 0 +0110CE:0110FF Other None 0 +011100:011102 Extend Extend 0 +011103:011126 Other Consonant 0 +011127:01112B Extend Extend 0 +01112C:01112C SpacingMark None 0 +01112D:011132 Extend Extend 0 +011133:011133 Extend Linker 0 +011134:011134 Extend Extend 0 +011135:011143 Other None 0 +011144:011144 Other Consonant 0 +011145:011146 SpacingMark None 0 +011147:011147 Other Consonant 0 +011148:011172 Other None 0 +011173:011173 Extend Extend 0 +011174:01117F Other None 0 +011180:011181 Extend Extend 0 +011182:011182 SpacingMark None 0 +011183:0111B2 Other None 0 +0111B3:0111B5 SpacingMark None 0 +0111B6:0111BE Extend Extend 0 +0111BF:0111BF SpacingMark None 0 +0111C0:0111C0 Extend Extend 0 +0111C1:0111C1 Other None 0 +0111C2:0111C3 Prepend None 0 +0111C4:0111C8 Other None 0 +0111C9:0111CC Extend Extend 0 +0111CD:0111CD Other None 0 +0111CE:0111CE SpacingMark None 0 +0111CF:0111CF Extend Extend 0 +0111D0:01122B Other None 0 +01122C:01122E SpacingMark None 0 +01122F:011231 Extend Extend 0 +011232:011233 SpacingMark None 0 +011234:011237 Extend Extend 0 +011238:01123D Other None 0 +01123E:01123E Extend Extend 0 +01123F:011240 Other None 0 +011241:011241 Extend Extend 0 +011242:0112DE Other None 0 +0112DF:0112DF Extend Extend 0 +0112E0:0112E2 SpacingMark None 0 +0112E3:0112EA Extend Extend 0 +0112EB:0112FF Other None 0 +011300:011301 Extend Extend 0 +011302:011303 SpacingMark None 0 +011304:01133A Other None 0 +01133B:01133C Extend Extend 0 +01133D:01133D Other None 0 +01133E:01133E Extend Extend 0 +01133F:01133F SpacingMark None 0 +011340:011340 Extend Extend 0 +011341:011344 SpacingMark None 0 +011345:011346 Other None 0 +011347:011348 SpacingMark None 0 +011349:01134A Other None 0 +01134B:01134C SpacingMark None 0 +01134D:01134D Extend Extend 0 +01134E:011356 Other None 0 +011357:011357 Extend Extend 0 +011358:011361 Other None 0 +011362:011363 SpacingMark None 0 +011364:011365 Other None 0 +011366:01136C Extend Extend 0 +01136D:01136F Other None 0 +011370:011374 Extend Extend 0 +011375:01137F Other None 0 +011380:011389 Other Consonant 0 +01138A:01138A Other None 0 +01138B:01138B Other Consonant 0 +01138C:01138D Other None 0 +01138E:01138E Other Consonant 0 +01138F:01138F Other None 0 +011390:0113B5 Other Consonant 0 +0113B6:0113B7 Other None 0 +0113B8:0113B8 Extend Extend 0 +0113B9:0113BA SpacingMark None 0 +0113BB:0113C0 Extend Extend 0 +0113C1:0113C1 Other None 0 +0113C2:0113C2 Extend Extend 0 +0113C3:0113C4 Other None 0 +0113C5:0113C5 Extend Extend 0 +0113C6:0113C6 Other None 0 +0113C7:0113C9 Extend Extend 0 +0113CA:0113CA SpacingMark None 0 +0113CB:0113CB Other None 0 +0113CC:0113CD SpacingMark None 0 +0113CE:0113CF Extend Extend 0 +0113D0:0113D0 Extend Linker 0 +0113D1:0113D1 Prepend None 0 +0113D2:0113D2 Extend Extend 0 +0113D3:0113E0 Other None 0 +0113E1:0113E2 Extend Extend 0 +0113E3:011434 Other None 0 +011435:011437 SpacingMark None 0 +011438:01143F Extend Extend 0 +011440:011441 SpacingMark None 0 +011442:011444 Extend Extend 0 +011445:011445 SpacingMark None 0 +011446:011446 Extend Extend 0 +011447:01145D Other None 0 +01145E:01145E Extend Extend 0 +01145F:0114AF Other None 0 +0114B0:0114B0 Extend Extend 0 +0114B1:0114B2 SpacingMark None 0 +0114B3:0114B8 Extend Extend 0 +0114B9:0114B9 SpacingMark None 0 +0114BA:0114BA Extend Extend 0 +0114BB:0114BC SpacingMark None 0 +0114BD:0114BD Extend Extend 0 +0114BE:0114BE SpacingMark None 0 +0114BF:0114C0 Extend Extend 0 +0114C1:0114C1 SpacingMark None 0 +0114C2:0114C3 Extend Extend 0 +0114C4:0115AE Other None 0 +0115AF:0115AF Extend Extend 0 +0115B0:0115B1 SpacingMark None 0 +0115B2:0115B5 Extend Extend 0 +0115B6:0115B7 Other None 0 +0115B8:0115BB SpacingMark None 0 +0115BC:0115BD Extend Extend 0 +0115BE:0115BE SpacingMark None 0 +0115BF:0115C0 Extend Extend 0 +0115C1:0115DB Other None 0 +0115DC:0115DD Extend Extend 0 +0115DE:01162F Other None 0 +011630:011632 SpacingMark None 0 +011633:01163A Extend Extend 0 +01163B:01163C SpacingMark None 0 +01163D:01163D Extend Extend 0 +01163E:01163E SpacingMark None 0 +01163F:011640 Extend Extend 0 +011641:0116AA Other None 0 +0116AB:0116AB Extend Extend 0 +0116AC:0116AC SpacingMark None 0 +0116AD:0116AD Extend Extend 0 +0116AE:0116AF SpacingMark None 0 +0116B0:0116B7 Extend Extend 0 +0116B8:01171C Other None 0 +01171D:01171D Extend Extend 0 +01171E:01171E SpacingMark None 0 +01171F:01171F Extend Extend 0 +011720:011721 Other None 0 +011722:011725 Extend Extend 0 +011726:011726 SpacingMark None 0 +011727:01172B Extend Extend 0 +01172C:01182B Other None 0 +01182C:01182E SpacingMark None 0 +01182F:011837 Extend Extend 0 +011838:011838 SpacingMark None 0 +011839:01183A Extend Extend 0 +01183B:0118FF Other None 0 +011900:011906 Other Consonant 0 +011907:011908 Other None 0 +011909:011909 Other Consonant 0 +01190A:01190B Other None 0 +01190C:011913 Other Consonant 0 +011914:011914 Other None 0 +011915:011916 Other Consonant 0 +011917:011917 Other None 0 +011918:01192F Other Consonant 0 +011930:011930 Extend Extend 0 +011931:011935 SpacingMark None 0 +011936:011936 Other None 0 +011937:011938 SpacingMark None 0 +011939:01193A Other None 0 +01193B:01193D Extend Extend 0 +01193E:01193E Extend Linker 0 +01193F:01193F Prepend None 0 +011940:011940 SpacingMark None 0 +011941:011941 Prepend None 0 +011942:011942 SpacingMark None 0 +011943:011943 Extend Extend 0 +011944:0119D0 Other None 0 +0119D1:0119D3 SpacingMark None 0 +0119D4:0119D7 Extend Extend 0 +0119D8:0119D9 Other None 0 +0119DA:0119DB Extend Extend 0 +0119DC:0119DF SpacingMark None 0 +0119E0:0119E0 Extend Extend 0 +0119E1:0119E3 Other None 0 +0119E4:0119E4 SpacingMark None 0 +0119E5:0119FF Other None 0 +011A00:011A00 Other Consonant 0 +011A01:011A0A Extend Extend 0 +011A0B:011A32 Other Consonant 0 +011A33:011A38 Extend Extend 0 +011A39:011A39 SpacingMark None 0 +011A3A:011A3A Other None 0 +011A3B:011A3E Extend Extend 0 +011A3F:011A46 Other None 0 +011A47:011A47 Extend Linker 0 +011A48:011A4F Other None 0 +011A50:011A50 Other Consonant 0 +011A51:011A56 Extend Extend 0 +011A57:011A58 SpacingMark None 0 +011A59:011A5B Extend Extend 0 +011A5C:011A83 Other Consonant 0 +011A84:011A89 Prepend None 0 +011A8A:011A96 Extend Extend 0 +011A97:011A97 SpacingMark None 0 +011A98:011A98 Extend Extend 0 +011A99:011A99 Extend Linker 0 +011A9A:011B5F Other None 0 +011B60:011B60 Extend Extend 0 +011B61:011B61 SpacingMark None 0 +011B62:011B64 Extend Extend 0 +011B65:011B65 SpacingMark None 0 +011B66:011B66 Extend Extend 0 +011B67:011B67 SpacingMark None 0 +011B68:011C2E Other None 0 +011C2F:011C2F SpacingMark None 0 +011C30:011C36 Extend Extend 0 +011C37:011C37 Other None 0 +011C38:011C3D Extend Extend 0 +011C3E:011C3E SpacingMark None 0 +011C3F:011C3F Extend Extend 0 +011C40:011C91 Other None 0 +011C92:011CA7 Extend Extend 0 +011CA8:011CA8 Other None 0 +011CA9:011CA9 SpacingMark None 0 +011CAA:011CB0 Extend Extend 0 +011CB1:011CB1 SpacingMark None 0 +011CB2:011CB3 Extend Extend 0 +011CB4:011CB4 SpacingMark None 0 +011CB5:011CB6 Extend Extend 0 +011CB7:011D30 Other None 0 +011D31:011D36 Extend Extend 0 +011D37:011D39 Other None 0 +011D3A:011D3A Extend Extend 0 +011D3B:011D3B Other None 0 +011D3C:011D3D Extend Extend 0 +011D3E:011D3E Other None 0 +011D3F:011D45 Extend Extend 0 +011D46:011D46 Prepend None 0 +011D47:011D47 Extend Extend 0 +011D48:011D89 Other None 0 +011D8A:011D8E SpacingMark None 0 +011D8F:011D8F Other None 0 +011D90:011D91 Extend Extend 0 +011D92:011D92 Other None 0 +011D93:011D94 SpacingMark None 0 +011D95:011D95 Extend Extend 0 +011D96:011D96 SpacingMark None 0 +011D97:011D97 Extend Extend 0 +011D98:011EF2 Other None 0 +011EF3:011EF4 Extend Extend 0 +011EF5:011EF6 SpacingMark None 0 +011EF7:011EFF Other None 0 +011F00:011F01 Extend Extend 0 +011F02:011F02 Prepend None 0 +011F03:011F03 SpacingMark None 0 +011F04:011F10 Other Consonant 0 +011F11:011F11 Other None 0 +011F12:011F33 Other Consonant 0 +011F34:011F35 SpacingMark None 0 +011F36:011F3A Extend Extend 0 +011F3B:011F3D Other None 0 +011F3E:011F3F SpacingMark None 0 +011F40:011F41 Extend Extend 0 +011F42:011F42 Extend Linker 0 +011F43:011F59 Other None 0 +011F5A:011F5A Extend Extend 0 +011F5B:01342F Other None 0 +013430:01343F Control None 0 +013440:013440 Extend Extend 0 +013441:013446 Other None 0 +013447:013455 Extend Extend 0 +013456:01611D Other None 0 +01611E:016129 Extend Extend 0 +01612A:01612C SpacingMark None 0 +01612D:01612F Extend Extend 0 +016130:016AEF Other None 0 +016AF0:016AF4 Extend Extend 0 +016AF5:016B2F Other None 0 +016B30:016B36 Extend Extend 0 +016B37:016D62 Other None 0 +016D63:016D63 V None 0 +016D64:016D66 Other None 0 +016D67:016D6A V None 0 +016D6B:016F4E Other None 0 +016F4F:016F4F Extend Extend 0 +016F50:016F50 Other None 0 +016F51:016F87 SpacingMark None 0 +016F88:016F8E Other None 0 +016F8F:016F92 Extend Extend 0 +016F93:016FE3 Other None 0 +016FE4:016FE4 Extend Extend 0 +016FE5:016FEF Other None 0 +016FF0:016FF1 Extend Extend 0 +016FF2:01BC9C Other None 0 +01BC9D:01BC9E Extend Extend 0 +01BC9F:01BC9F Other None 0 +01BCA0:01BCA3 Control None 0 +01BCA4:01CEFF Other None 0 +01CF00:01CF2D Extend Extend 0 +01CF2E:01CF2F Other None 0 +01CF30:01CF46 Extend Extend 0 +01CF47:01D164 Other None 0 +01D165:01D169 Extend Extend 0 +01D16A:01D16C Other None 0 +01D16D:01D172 Extend Extend 0 +01D173:01D17A Control None 0 +01D17B:01D182 Extend Extend 0 +01D183:01D184 Other None 0 +01D185:01D18B Extend Extend 0 +01D18C:01D1A9 Other None 0 +01D1AA:01D1AD Extend Extend 0 +01D1AE:01D241 Other None 0 +01D242:01D244 Extend Extend 0 +01D245:01D9FF Other None 0 +01DA00:01DA36 Extend Extend 0 +01DA37:01DA3A Other None 0 +01DA3B:01DA6C Extend Extend 0 +01DA6D:01DA74 Other None 0 +01DA75:01DA75 Extend Extend 0 +01DA76:01DA83 Other None 0 +01DA84:01DA84 Extend Extend 0 +01DA85:01DA9A Other None 0 +01DA9B:01DA9F Extend Extend 0 +01DAA0:01DAA0 Other None 0 +01DAA1:01DAAF Extend Extend 0 +01DAB0:01DFFF Other None 0 +01E000:01E006 Extend Extend 0 +01E007:01E007 Other None 0 +01E008:01E018 Extend Extend 0 +01E019:01E01A Other None 0 +01E01B:01E021 Extend Extend 0 +01E022:01E022 Other None 0 +01E023:01E024 Extend Extend 0 +01E025:01E025 Other None 0 +01E026:01E02A Extend Extend 0 +01E02B:01E08E Other None 0 +01E08F:01E08F Extend Extend 0 +01E090:01E12F Other None 0 +01E130:01E136 Extend Extend 0 +01E137:01E2AD Other None 0 +01E2AE:01E2AE Extend Extend 0 +01E2AF:01E2EB Other None 0 +01E2EC:01E2EF Extend Extend 0 +01E2F0:01E4EB Other None 0 +01E4EC:01E4EF Extend Extend 0 +01E4F0:01E5ED Other None 0 +01E5EE:01E5EF Extend Extend 0 +01E5F0:01E6E2 Other None 0 +01E6E3:01E6E3 Extend Extend 0 +01E6E4:01E6E5 Other None 0 +01E6E6:01E6E6 Extend Extend 0 +01E6E7:01E6ED Other None 0 +01E6EE:01E6EF Extend Extend 0 +01E6F0:01E6F4 Other None 0 +01E6F5:01E6F5 Extend Extend 0 +01E6F6:01E8CF Other None 0 +01E8D0:01E8D6 Extend Extend 0 +01E8D7:01E943 Other None 0 +01E944:01E94A Extend Extend 0 +01E94B:01F003 Other None 0 +01F004:01F004 Other None 1 +01F005:01F02B Other None 0 +01F02C:01F02F Other None 1 +01F030:01F093 Other None 0 +01F094:01F09F Other None 1 +01F0A0:01F0AE Other None 0 +01F0AF:01F0B0 Other None 1 +01F0B1:01F0BF Other None 0 +01F0C0:01F0C0 Other None 1 +01F0C1:01F0CE Other None 0 +01F0CF:01F0D0 Other None 1 +01F0D1:01F0F5 Other None 0 +01F0F6:01F0FF Other None 1 +01F100:01F16F Other None 0 +01F170:01F171 Other None 1 +01F172:01F17D Other None 0 +01F17E:01F17F Other None 1 +01F180:01F18D Other None 0 +01F18E:01F18E Other None 1 +01F18F:01F190 Other None 0 +01F191:01F19A Other None 1 +01F19B:01F1AD Other None 0 +01F1AE:01F1E5 Other None 1 +01F1E6:01F1FF Regional_Indicator None 0 +01F200:01F200 Other None 0 +01F201:01F20F Other None 1 +01F210:01F219 Other None 0 +01F21A:01F21A Other None 1 +01F21B:01F22E Other None 0 +01F22F:01F22F Other None 1 +01F230:01F231 Other None 0 +01F232:01F23A Other None 1 +01F23B:01F23B Other None 0 +01F23C:01F23F Other None 1 +01F240:01F248 Other None 0 +01F249:01F25F Other None 1 +01F260:01F265 Other None 0 +01F266:01F321 Other None 1 +01F322:01F323 Other None 0 +01F324:01F393 Other None 1 +01F394:01F395 Other None 0 +01F396:01F397 Other None 1 +01F398:01F398 Other None 0 +01F399:01F39B Other None 1 +01F39C:01F39D Other None 0 +01F39E:01F3F0 Other None 1 +01F3F1:01F3F2 Other None 0 +01F3F3:01F3F5 Other None 1 +01F3F6:01F3F6 Other None 0 +01F3F7:01F3FA Other None 1 +01F3FB:01F3FF Extend Extend 0 +01F400:01F4FD Other None 1 +01F4FE:01F4FE Other None 0 +01F4FF:01F53D Other None 1 +01F53E:01F548 Other None 0 +01F549:01F54E Other None 1 +01F54F:01F54F Other None 0 +01F550:01F567 Other None 1 +01F568:01F56E Other None 0 +01F56F:01F570 Other None 1 +01F571:01F572 Other None 0 +01F573:01F57A Other None 1 +01F57B:01F586 Other None 0 +01F587:01F587 Other None 1 +01F588:01F589 Other None 0 +01F58A:01F58D Other None 1 +01F58E:01F58F Other None 0 +01F590:01F590 Other None 1 +01F591:01F594 Other None 0 +01F595:01F596 Other None 1 +01F597:01F5A3 Other None 0 +01F5A4:01F5A5 Other None 1 +01F5A6:01F5A7 Other None 0 +01F5A8:01F5A8 Other None 1 +01F5A9:01F5B0 Other None 0 +01F5B1:01F5B2 Other None 1 +01F5B3:01F5BB Other None 0 +01F5BC:01F5BC Other None 1 +01F5BD:01F5C1 Other None 0 +01F5C2:01F5C4 Other None 1 +01F5C5:01F5D0 Other None 0 +01F5D1:01F5D3 Other None 1 +01F5D4:01F5DB Other None 0 +01F5DC:01F5DE Other None 1 +01F5DF:01F5E0 Other None 0 +01F5E1:01F5E1 Other None 1 +01F5E2:01F5E2 Other None 0 +01F5E3:01F5E3 Other None 1 +01F5E4:01F5E7 Other None 0 +01F5E8:01F5E8 Other None 1 +01F5E9:01F5EE Other None 0 +01F5EF:01F5EF Other None 1 +01F5F0:01F5F2 Other None 0 +01F5F3:01F5F3 Other None 1 +01F5F4:01F5F9 Other None 0 +01F5FA:01F64F Other None 1 +01F650:01F67F Other None 0 +01F680:01F6C5 Other None 1 +01F6C6:01F6CA Other None 0 +01F6CB:01F6D2 Other None 1 +01F6D3:01F6D4 Other None 0 +01F6D5:01F6E5 Other None 1 +01F6E6:01F6E8 Other None 0 +01F6E9:01F6E9 Other None 1 +01F6EA:01F6EA Other None 0 +01F6EB:01F6F0 Other None 1 +01F6F1:01F6F2 Other None 0 +01F6F3:01F6FF Other None 1 +01F700:01F7D9 Other None 0 +01F7DA:01F7FF Other None 1 +01F800:01F80B Other None 0 +01F80C:01F80F Other None 1 +01F810:01F847 Other None 0 +01F848:01F84F Other None 1 +01F850:01F859 Other None 0 +01F85A:01F85F Other None 1 +01F860:01F887 Other None 0 +01F888:01F88F Other None 1 +01F890:01F8AD Other None 0 +01F8AE:01F8AF Other None 1 +01F8B0:01F8BB Other None 0 +01F8BC:01F8BF Other None 1 +01F8C0:01F8C1 Other None 0 +01F8C2:01F8CF Other None 1 +01F8D0:01F8D8 Other None 0 +01F8D9:01F8FF Other None 1 +01F900:01F90B Other None 0 +01F90C:01F93A Other None 1 +01F93B:01F93B Other None 0 +01F93C:01F945 Other None 1 +01F946:01F946 Other None 0 +01F947:01F9FF Other None 1 +01FA00:01FA57 Other None 0 +01FA58:01FA5F Other None 1 +01FA60:01FA6D Other None 0 +01FA6E:01FAFF Other None 1 +01FB00:01FBFF Other None 0 +01FC00:01FFFD Other None 1 +01FFFE:0DFFFF Other None 0 +0E0000:0E001F Control None 0 +0E0020:0E007F Extend Extend 0 +0E0080:0E00FF Control None 0 +0E0100:0E01EF Extend Extend 0 +0E01F0:0E0FFF Control None 0 +0E1000:10FFFF Other None 0 diff --git a/crates/unicode/tests/differential.rs b/crates/unicode/tests/differential.rs index 1bed13e7faf..0d66c1eff14 100644 --- a/crates/unicode/tests/differential.rs +++ b/crates/unicode/tests/differential.rs @@ -11,7 +11,7 @@ //! Both data files use the same run-length format: one `predicate` line per //! str method, followed by comma-separated hex `start:end` inclusive ranges. -// spell-checker:ignore recategorized recategorizations +// spell-checker:ignore recategorized recategorizations incb #[cfg(test)] mod tests { @@ -421,4 +421,85 @@ mod tests { panic!("{msg}"); } } + + #[test] + fn unicode17_grapheme_properties_exact_final() { + use rustpython_unicode::grapheme; + use rustpython_wtf8::CodePoint; + + const MAX: u32 = 0x110000; + const REFERENCE: &str = include_str!("data/cpython3.15_graphemes.txt"); + const GCB: [&str; 14] = [ + "Other", + "Prepend", + "CR", + "LF", + "Control", + "Extend", + "Regional_Indicator", + "SpacingMark", + "L", + "V", + "T", + "LV", + "LVT", + "ZWJ", + ]; + const INCB: [&str; 4] = ["None", "Linker", "Consonant", "Extend"]; + assert!(REFERENCE.len() <= 1024 * 1024, "oracle size bound"); + assert_eq!( + REFERENCE.lines().next(), + Some("# CPython d220409a13b77de7a6c30abe16ea90db01528edc; Unicode 17.0.0"), + "wrong reference provenance" + ); + let mut next = 0; + let mut rows = 0; + let mut mismatches = 0usize; + let mut first = Vec::new(); + for line in REFERENCE.lines().filter(|line| !line.starts_with('#')) { + rows += 1; + assert!(rows <= 100000, "oracle range count bound"); + let fields: Vec<_> = line.split_whitespace().collect(); + assert_eq!(fields.len(), 4, "invalid oracle row"); + let (start, end) = fields[0].split_once(':').expect("start:end"); + let start = u32::from_str_radix(start, 16).expect("hex start"); + let end = u32::from_str_radix(end, 16).expect("hex end"); + assert_eq!(start, next, "gap, overlap, or unordered oracle range"); + assert!(start <= end && end < MAX, "invalid oracle range"); + assert!( + GCB.contains(&fields[1]) && INCB.contains(&fields[2]), + "invalid property name" + ); + let pictographic = match fields[3] { + "0" => false, + "1" => true, + _ => panic!("invalid Extended_Pictographic value"), + }; + for cp in start..=end { + // Includes surrogates; converting through char would lose coverage. + let point = CodePoint::from_u32(cp).expect("valid code point"); + let actual = ( + grapheme::grapheme_cluster_break(point), + grapheme::indic_conjunct_break(point), + grapheme::extended_pictographic(point), + ); + let expected = (fields[1], fields[2], pictographic); + if actual != expected { + mismatches += 1; + if first.len() < 20 { + first.push(format!("U+{cp:06X}: expected {expected:?}, got {actual:?}")); + } + } + } + next = end + 1; + } + assert_eq!(next, MAX, "oracle does not cover every code point"); + assert_eq!( + mismatches, + 0, + "first {} mismatches: {}", + first.len(), + first.join("; ") + ); + } } diff --git a/crates/unicode/tests/generate_reference.py b/crates/unicode/tests/generate_reference.py index 6a78230de75..4f881153c9c 100644 --- a/crates/unicode/tests/generate_reference.py +++ b/crates/unicode/tests/generate_reference.py @@ -21,6 +21,176 @@ MAX = 0x110000 + +def generate_graphemes(args) -> int: + """Write a new Unicode 17 oracle, without touching the older fixtures.""" + import hashlib + import json + import subprocess + + commit = "d220409a13b77de7a6c30abe16ea90db01528edc" + gcb_names = ( + "Other", + "Prepend", + "CR", + "LF", + "Control", + "Extend", + "Regional_Indicator", + "SpacingMark", + "L", + "V", + "T", + "LV", + "LVT", + "ZWJ", + ) + incb_names = ("None", "Linker", "Consonant", "Extend") + + def digest(path): + h = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(65536), b""): + h.update(chunk) + return h.hexdigest() + + if sys.implementation.name != "cpython" or unicodedata.unidata_version != "17.0.0": + raise RuntimeError("requires the reviewed CPython Unicode 17 oracle") + executable = pathlib.Path(sys.executable).resolve(strict=True) + extension = pathlib.Path(unicodedata.__file__).resolve(strict=True) + if ( + digest(executable) != args.python_sha256 + or digest(extension) != args.unicodedata_sha256 + ): + raise RuntimeError("oracle executable or extension hash mismatch") + source = args.cpython_source.resolve(strict=True) + head = subprocess.check_output( + ["git", "-C", str(source), "rev-parse", "HEAD"], + timeout=5, + text=True, + ).strip() + if head != commit: + raise RuntimeError("CPython source is not the pinned final commit") + header = source / "Modules/unicodedata_db.h" + blob = subprocess.check_output( + ["git", "-C", str(source), "show", f"{commit}:Modules/unicodedata_db.h"], + timeout=5, + ) + if digest(header) != hashlib.sha256(blob).hexdigest(): + raise RuntimeError("CPython Unicode database header differs from pinned source") + + outputs = [ + args.graphemes_output, + args.graphemes_raw_output, + args.graphemes_manifest, + ] + outputs = [p.resolve() for p in outputs] + if len(set(outputs)) != 3: + raise RuntimeError("oracle output paths must be distinct") + old_data = pathlib.Path(__file__).resolve().parent / "data" + for path in outputs: + if path.exists() or path.is_relative_to(old_data): + raise RuntimeError( + "outputs must be new files outside existing fixture directory" + ) + if not path.parent.is_dir(): + raise RuntimeError("output parents must already exist") + + range_path, raw_path, manifest_path = outputs + ranges = [] + start = 0 + previous = None + raw_hash = hashlib.sha256() + # Exclusive creation prevents replacing any existing reference artifact. + with raw_path.open("xb") as raw: + buffer = bytearray() + for cp in range(MAX): + c = chr(cp) + triple = ( + gcb_names.index(unicodedata.grapheme_cluster_break(c)), + incb_names.index(unicodedata.indic_conjunct_break(c)), + int(unicodedata.extended_pictographic(c)), + ) + if triple != previous: + if previous is not None: + ranges.append((start, cp - 1, previous)) + if len(ranges) > 100000: + raise RuntimeError("range oracle exceeds reviewed count bound") + start, previous = cp, triple + buffer.extend(triple) + if len(buffer) >= 65535: + raw.write(buffer) + raw_hash.update(buffer) + buffer.clear() + raw.write(buffer) + raw_hash.update(buffer) + ranges.append((start, MAX - 1, previous)) + if raw_path.stat().st_size != MAX * 3: + raise RuntimeError("raw oracle length mismatch") + lines = [ + f"# CPython {commit}; Unicode 17.0.0", + "# Inclusive contiguous ranges: start:end GCB InCB Extended_Pictographic", + f"# Reconstructed raw SHA256 {raw_hash.hexdigest()}", + ] + for start, end, (gcb, incb, pictographic) in ranges: + lines.append( + f"{start:06X}:{end:06X} {gcb_names[gcb]} {incb_names[incb]} {pictographic}" + ) + text = ("\n".join(lines) + "\n").encode("ascii") + if len(text) > 1024 * 1024 or len(ranges) > 100000: + raise RuntimeError("range oracle exceeds reviewed size bound") + reconstructed = hashlib.sha256() + next_cp = 0 + for line in text.decode("ascii").splitlines(): + if line.startswith("#"): + continue + bounds, gcb, incb, pictographic = line.split() + first, last = (int(value, 16) for value in bounds.split(":")) + if first != next_cp or not first <= last < MAX: + raise RuntimeError("range encoding has a gap, overlap, or invalid bound") + triple = bytes( + (gcb_names.index(gcb), incb_names.index(incb), int(pictographic)) + ) + reconstructed.update(triple * (last - first + 1)) + next_cp = last + 1 + if next_cp != MAX or reconstructed.hexdigest() != raw_hash.hexdigest(): + raise RuntimeError("range reconstruction differs from the raw property oracle") + with range_path.open("xb") as output: + output.write(text) + manifest = { + "cpython_commit": commit, + "unicode_version": unicodedata.unidata_version, + "runtime_version": sys.version, + "runtime_git": getattr(sys, "_git", None), + "python": str(executable), + "python_sha256": digest(executable), + "unicodedata": str(extension), + "unicodedata_sha256": digest(extension), + "database_header_sha256": digest(header), + "generator_sha256": digest(pathlib.Path(__file__)), + "codepoints": MAX, + "properties": 3, + "ranges": len(ranges), + "gcb_encoding": gcb_names, + "incb_encoding": incb_names, + "raw_bytes": MAX * 3, + "raw_sha256": raw_hash.hexdigest(), + "ranges_bytes": len(text), + "ranges_sha256": hashlib.sha256(text).hexdigest(), + } + manifest_bytes = (json.dumps(manifest, indent=2) + "\n").encode("utf-8") + # Total artifact bound, independent of the launcher's per-file rlimit. + if ( + len(manifest_bytes) > 16384 + or MAX * 3 + len(text) + len(manifest_bytes) > 4_407_296 + ): + raise RuntimeError("total oracle artifact budget exceeded") + with manifest_path.open("xb") as output: + output.write(manifest_bytes) + print(json.dumps(manifest, sort_keys=True)) + return 0 + + # str predicates: name -> single-char method. STR_PREDICATES = { "isalpha": str.isalpha, @@ -111,4 +281,18 @@ def main() -> int: if __name__ == "__main__": - raise SystemExit(main()) + if len(sys.argv) == 1: + raise SystemExit(main()) + import argparse + + parser = argparse.ArgumentParser( + description="Generate only a new grapheme property oracle" + ) + parser.add_argument("--graphemes-only", action="store_true", required=True) + parser.add_argument("--graphemes-output", type=pathlib.Path, required=True) + parser.add_argument("--graphemes-raw-output", type=pathlib.Path, required=True) + parser.add_argument("--graphemes-manifest", type=pathlib.Path, required=True) + parser.add_argument("--cpython-source", type=pathlib.Path, required=True) + parser.add_argument("--python-sha256", required=True) + parser.add_argument("--unicodedata-sha256", required=True) + raise SystemExit(generate_graphemes(parser.parse_args()))