diff options
Diffstat (limited to 'text/syllabic.py')
| -rw-r--r-- | text/syllabic.py | 105 |
1 files changed, 98 insertions, 7 deletions
diff --git a/text/syllabic.py b/text/syllabic.py index 4a79869..b4de81a 100644 --- a/text/syllabic.py +++ b/text/syllabic.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""The tables HarfBuzz's syllabic shapers read, as C (text/syllabic-data.c). +"""The tables HarfBuzz's complex shapers read, as C (text/syllabic-data.c). python3 text/syllabic.py HARFBUZZ_SRC > text/syllabic-data.c python3 text/syllabic.py HARFBUZZ_SRC --check @@ -16,6 +16,10 @@ HARFBUZZ_SRC is HarfBuzz 10.2.0's src/ directory; what is read from it: shaper-use-table.hh, whose arrays and lookup (gen-use-table.py's) are carried over as they are, the categories named in them resolved. +- Arabic's: each character's joining type (with Syriac's Alaph and + Dalath-Rish groups), and the presentation forms and ligatures fallback + shaping builds lookups from (hb-ot-shaper-arabic-table.hh). + - The syllable machines: hb-ot-shaper-*-machine.rl, the Ragel grammars. This compiles each to a DFA with Ragel's scanner semantics (the longest match from each position, ties to the pattern listed first), its symbols @@ -242,6 +246,29 @@ def scan(table, accept, symbols): return found +# ---- HarfBuzz's generated tables: a flat array (rows commented with the +# code point they start at, a row's first cells left blank where a range +# starts inside it) and a lookup of ranges into it + +def ranged_table(text, array, function, cell): + """Code point to cell for a table whose lookup function returns + array[u - first + offset] over ranges; cell() reads one cell's text.""" + body = text[text.index(f'{array}[] =') :] + body = body[body.index('{') + 1:body.index('\n};')] + body = re.sub(r'/\*.*?\*/', ' ', body, flags=re.S) + body = re.sub(r'#define[^\n]*', ' ', body) + cells = cell(body) + offsets = {int(k, 16): int(v) for k, v in re.findall(rf'#define {array.split("_")[0]}_offset_0x([0-9a-f]+)u (\d+)', text)} + lookup = text[text.index(function):] + table = {} + for first, last, key in re.findall(r'hb_in_range<hb_codepoint_t> \(u, 0x([0-9A-F]+)u, 0x([0-9A-F]+)u\)\) return ' + rf'{array}\[u - 0x[0-9A-F]+u \+ \w+_offset_0x([0-9a-f]+)u\]', lookup): + first, last, at = int(first, 16), int(last, 16), offsets[int(key, 16)] + for u in range(first, last + 1): + table[u] = cells[at + u - first] + return table, lookup + + # ---- The Indic table def indic_table(grammars): @@ -258,12 +285,8 @@ def indic_table(grammars): def cell(s, m): return short['_OT_' + s], short['_POS_' + m] - cells = {} - table = text[text.index('static const uint16_t indic_table[]'):text.index('}; /* Table items:')] - for start, row in re.findall(r'/\* ([0-9A-F]{4,5}) \*/(.*)', table): - for k, (s, m) in enumerate(re.findall(r'_\((\w+),(\w+)\)', row)): - cells[int(start, 16) + k] = cell(s, m) - lookup = text[text.index('hb_indic_get_categories (hb_codepoint_t u)'):] + cells, lookup = ranged_table(text, 'indic_table', 'hb_indic_get_categories (hb_codepoint_t u)', + lambda body: [cell(s, m) for s, m in re.findall(r'_\((\w+),(\w+)\)', body)]) for code, s, m in re.findall(r'u == 0x([0-9A-F]+)u\)\) return _\((\w+),(\w+)\);', lookup): cells[int(code, 16)] = cell(s, m) default = cell(*re.search(r'return _\((\w+),(\w+)\);\s*\}', lookup).groups()) @@ -297,6 +320,34 @@ def use_table(grammar): return arrays, expression[:-1] + str(grammar.symbols['O']) +def arabic_tables(): + """hb-ot-shaper-arabic-table.hh: joining type by code point (U 0, L 1, + R 2, D and C 3, Alaph 4, Dalath-Rish 5, T 7, X 8 elsewhere); the + presentation forms of U+0621..06D3 (init, medi, fina, isol); the + ligature sets, each as (first, ligature, components).""" + text = (HB / 'hb-ot-shaper-arabic-table.hh').read_text() + values = {'U': 0, 'L': 1, 'R': 2, 'D': 3, 'C': 3, 'A': 4, 'DR': 5, 'T': 7, 'X': 8} + joining, _ = ranged_table(text, 'joining_table', 'joining_type (hb_codepoint_t u)', + lambda body: [values[n] for n in re.findall(r'[A-Z]+', body)]) + shaping = [tuple(int(v, 16) for v in re.findall(r'0x([0-9A-F]{4})u', row)) + for row in re.findall(r'\{(0x[^}]*)\}, /\* U\+', text[text.index('shaping_table[][4]'):])] + first = int(re.search(r'SHAPING_TABLE_FIRST\s+0x([0-9A-F]+)u', text).group(1), 16) + last = int(re.search(r'SHAPING_TABLE_LAST\s+0x([0-9A-F]+)u', text).group(1), 16) + assert len(shaping) == last - first + 1 and all(len(r) == 4 for r in shaping) + ligatures = {} + for name in ('ligature_table', 'ligature_mark_table', 'ligature_3_table'): + body = text[text.index(f'}} {name}[] =') :] + body = body[:body.index('\n};')] + sets = [] + for first_u, entries in re.findall(r'\{ 0x([0-9A-F]+)u, \{(.*?)\}\},', body, re.S): + for components, ligature in re.findall(r'\{ \{([^}]*)\}, 0x([0-9A-F]+)u ?\}', entries): + sets.append((int(first_u, 16), int(ligature, 16), + [int(c, 16) for c in re.findall(r'0x([0-9A-F]+)u', components)])) + assert sets, name + ligatures[name] = sets + return joining, (first, shaping), ligatures + + # ---- Checking against Ragel's machines (hb-ot-shaper-*-machine.hh) def ragel(name): @@ -476,6 +527,46 @@ def main(): out.append('static unsigned use_b4(const uint8_t *a, unsigned i) { return (a[i >> 1] >> ((i & 1u) << 2)) & 15u; }') out.append(f'uint8_t vega_use_category(uint32_t u) {{ return (uint8_t)({expression}); }}') out.append('') + joining, (first, shaping), ligatures = arabic_tables() + runs, flat = [], [] + for a, b in runs_of(joining): + runs.append((a, b, len(flat))) + flat.extend(joining.get(code, 8) for code in range(a, b + 1)) + out.append('/* Arabic joining types (hb-ot-shaper-arabic-table.hh, Unicode 16): U 0, L 1, R 2, D and C 3,') + out.append(' * Alaph 4, Dalath-Rish 5, T 7; 8 where the general category decides. */') + out.append(f'static const uint8_t joining_cells[{len(flat)}] = {{') + for k in range(0, len(flat), 32): + out.append(' ' + ', '.join(str(v) for v in flat[k:k + 32]) + ',') + out.append('};') + out.append(f'static const struct {{ uint32_t first, last, at; }} joining_runs[{len(runs)}] = {{') + for a, b, at in runs: + out.append(f' {{0x{a:04X}, 0x{b:04X}, {at}}},') + out.append('};') + out.append('uint8_t vega_arabic_joining(uint32_t u) {') + out.append(f' for (unsigned i = 0; i < {len(runs)} && u >= joining_runs[i].first; ++i)') + out.append(' if (u <= joining_runs[i].last) return joining_cells[joining_runs[i].at + u - joining_runs[i].first];') + out.append(' return 8;') + out.append('}') + out.append('') + out.append(f'/* Presentation forms of U+{first:04X}..U+{first + len(shaping) - 1:04X}: init, medi, fina, isol. */') + out.append(f'const uint16_t vega_arabic_forms[{len(shaping)}][4] = {{') + for row in shaping: + out.append(' {' + ', '.join(f'0x{v:04X}' for v in row) + '},') + out.append('};') + out.append('/* Ligatures fallback shaping forms: three components, two, and marks. */') + for name, cname in (('ligature_3_table', 'three'), ('ligature_table', 'two'), ('ligature_mark_table', 'marks')): + sets = ligatures[name] + out.append(f'static const VegaArabicLigature ligatures_{cname}[{len(sets)}] = {{') + for first_u, ligature, components in sets: + cs = components + [0] * (2 - len(components)) + out.append(f' {{0x{first_u:04X}, 0x{ligature:04X}, {{0x{cs[0]:04X}, 0x{cs[1]:04X}}}}},') + out.append('};') + out.append('const VegaArabicLigatures vega_arabic_ligatures[3] = {') + for name, cname, n in (('ligature_3_table', 'three', 3), ('ligature_table', 'two', 2), + ('ligature_mark_table', 'marks', 2)): + out.append(f' {{ligatures_{cname}, {len(ligatures[name])}, {n}}},') + out.append('};') + out.append('') out.append('/* The categories syllabic.h names are the machines\' own. */') for name, prefix in (('indic', 'VEGA_OT_'), ('khmer', 'VEGA_OT_'), ('myanmar', 'VEGA_OT_'), ('use', 'VEGA_USE_')): for symbol, value in sorted(grammars[name].symbols.items()): |
