Imported Upstream version 8.2.2
[platform/upstream/harfbuzz.git] / src / gen-indic-table.py
1 #!/usr/bin/env python3
2
3 """usage: ./gen-indic-table.py IndicSyllabicCategory.txt IndicPositionalCategory.txt Blocks.txt
4
5 Input files:
6 * https://unicode.org/Public/UCD/latest/ucd/IndicSyllabicCategory.txt
7 * https://unicode.org/Public/UCD/latest/ucd/IndicPositionalCategory.txt
8 * https://unicode.org/Public/UCD/latest/ucd/Blocks.txt
9 """
10
11 import sys
12
13 if len (sys.argv) != 4:
14         sys.exit (__doc__)
15
16 ALLOWED_SINGLES = [0x00A0, 0x25CC]
17 ALLOWED_BLOCKS = [
18         'Basic Latin',
19         'Latin-1 Supplement',
20         'Devanagari',
21         'Bengali',
22         'Gurmukhi',
23         'Gujarati',
24         'Oriya',
25         'Tamil',
26         'Telugu',
27         'Kannada',
28         'Malayalam',
29         'Myanmar',
30         'Khmer',
31         'Vedic Extensions',
32         'General Punctuation',
33         'Superscripts and Subscripts',
34         'Devanagari Extended',
35         'Myanmar Extended-B',
36         'Myanmar Extended-A',
37 ]
38
39 files = [open (x, encoding='utf-8') for x in sys.argv[1:]]
40
41 headers = [[f.readline () for i in range (2)] for f in files]
42
43 unicode_data = [{} for _ in files]
44 for i, f in enumerate (files):
45         for line in f:
46
47                 j = line.find ('#')
48                 if j >= 0:
49                         line = line[:j]
50
51                 fields = [x.strip () for x in line.split (';')]
52                 if len (fields) == 1:
53                         continue
54
55                 uu = fields[0].split ('..')
56                 start = int (uu[0], 16)
57                 if len (uu) == 1:
58                         end = start
59                 else:
60                         end = int (uu[1], 16)
61
62                 t = fields[1]
63
64                 for u in range (start, end + 1):
65                         unicode_data[i][u] = t
66
67 # Merge data into one dict:
68 defaults = ('Other', 'Not_Applicable', 'No_Block')
69 combined = {}
70 for i,d in enumerate (unicode_data):
71         for u,v in d.items ():
72                 if i == 2 and not u in combined:
73                         continue
74                 if not u in combined:
75                         combined[u] = list (defaults)
76                 combined[u][i] = v
77 combined = {k:v for k,v in combined.items() if k in ALLOWED_SINGLES or v[2] in ALLOWED_BLOCKS}
78
79
80 # Convert categories & positions types
81
82 categories = {
83   'indic' : [
84     'X',
85     'C',
86     'V',
87     'N',
88     'H',
89     'ZWNJ',
90     'ZWJ',
91     'M',
92     'SM',
93     'A',
94     'VD',
95     'PLACEHOLDER',
96     'DOTTEDCIRCLE',
97     'RS',
98     'MPst',
99     'Repha',
100     'Ra',
101     'CM',
102     'Symbol',
103     'CS',
104   ],
105   'khmer' : [
106     'VAbv',
107     'VBlw',
108     'VPre',
109     'VPst',
110
111     'Robatic',
112     'Xgroup',
113     'Ygroup',
114   ],
115   'myanmar' : [
116     'VAbv',
117     'VBlw',
118     'VPre',
119     'VPst',
120
121     'IV',
122     'As',
123     'DB',
124     'GB',
125     'MH',
126     'MR',
127     'MW',
128     'MY',
129     'PT',
130     'VS',
131     'ML',
132   ],
133 }
134
135 category_map = {
136   'Other'                       : 'X',
137   'Avagraha'                    : 'Symbol',
138   'Bindu'                       : 'SM',
139   'Brahmi_Joining_Number'       : 'PLACEHOLDER', # Don't care.
140   'Cantillation_Mark'           : 'A',
141   'Consonant'                   : 'C',
142   'Consonant_Dead'              : 'C',
143   'Consonant_Final'             : 'CM',
144   'Consonant_Head_Letter'       : 'C',
145   'Consonant_Initial_Postfixed' : 'C', # TODO
146   'Consonant_Killer'            : 'M', # U+17CD only.
147   'Consonant_Medial'            : 'CM',
148   'Consonant_Placeholder'       : 'PLACEHOLDER',
149   'Consonant_Preceding_Repha'   : 'Repha',
150   'Consonant_Prefixed'          : 'X', # Don't care.
151   'Consonant_Subjoined'         : 'CM',
152   'Consonant_Succeeding_Repha'  : 'CM',
153   'Consonant_With_Stacker'      : 'CS',
154   'Gemination_Mark'             : 'SM', # https://github.com/harfbuzz/harfbuzz/issues/552
155   'Invisible_Stacker'           : 'H',
156   'Joiner'                      : 'ZWJ',
157   'Modifying_Letter'            : 'X',
158   'Non_Joiner'                  : 'ZWNJ',
159   'Nukta'                       : 'N',
160   'Number'                      : 'PLACEHOLDER',
161   'Number_Joiner'               : 'PLACEHOLDER', # Don't care.
162   'Pure_Killer'                 : 'M', # Is like a vowel matra.
163   'Register_Shifter'            : 'RS',
164   'Syllable_Modifier'           : 'SM',
165   'Tone_Letter'                 : 'X',
166   'Tone_Mark'                   : 'N',
167   'Virama'                      : 'H',
168   'Visarga'                     : 'SM',
169   'Vowel'                       : 'V',
170   'Vowel_Dependent'             : 'M',
171   'Vowel_Independent'           : 'V',
172 }
173 position_map = {
174   'Not_Applicable'              : 'END',
175
176   'Left'                        : 'PRE_C',
177   'Top'                         : 'ABOVE_C',
178   'Bottom'                      : 'BELOW_C',
179   'Right'                       : 'POST_C',
180
181   # These should resolve to the position of the last part of the split sequence.
182   'Bottom_And_Right'            : 'POST_C',
183   'Left_And_Right'              : 'POST_C',
184   'Top_And_Bottom'              : 'BELOW_C',
185   'Top_And_Bottom_And_Left'     : 'BELOW_C',
186   'Top_And_Bottom_And_Right'    : 'POST_C',
187   'Top_And_Left'                : 'ABOVE_C',
188   'Top_And_Left_And_Right'      : 'POST_C',
189   'Top_And_Right'               : 'POST_C',
190
191   'Overstruck'                  : 'AFTER_MAIN',
192   'Visual_order_left'           : 'PRE_M',
193 }
194
195 category_overrides = {
196
197   # These are the variation-selectors. They only appear in the Myanmar grammar
198   # but are not Myanmar-specific
199   0xFE00: 'VS',
200   0xFE01: 'VS',
201   0xFE02: 'VS',
202   0xFE03: 'VS',
203   0xFE04: 'VS',
204   0xFE05: 'VS',
205   0xFE06: 'VS',
206   0xFE07: 'VS',
207   0xFE08: 'VS',
208   0xFE09: 'VS',
209   0xFE0A: 'VS',
210   0xFE0B: 'VS',
211   0xFE0C: 'VS',
212   0xFE0D: 'VS',
213   0xFE0E: 'VS',
214   0xFE0F: 'VS',
215
216   # These appear in the OT Myanmar spec, but are not Myanmar-specific
217   0x2015: 'PLACEHOLDER',
218   0x2022: 'PLACEHOLDER',
219   0x25FB: 'PLACEHOLDER',
220   0x25FC: 'PLACEHOLDER',
221   0x25FD: 'PLACEHOLDER',
222   0x25FE: 'PLACEHOLDER',
223
224
225   # Indic
226
227   0x0930: 'Ra', # Devanagari
228   0x09B0: 'Ra', # Bengali
229   0x09F0: 'Ra', # Bengali
230   0x0A30: 'Ra', # Gurmukhi      No Reph
231   0x0AB0: 'Ra', # Gujarati
232   0x0B30: 'Ra', # Oriya
233   0x0BB0: 'Ra', # Tamil         No Reph
234   0x0C30: 'Ra', # Telugu        Reph formed only with ZWJ
235   0x0CB0: 'Ra', # Kannada
236   0x0D30: 'Ra', # Malayalam     No Reph, Logical Repha
237
238   # The following act more like the Bindus.
239   0x0953: 'SM',
240   0x0954: 'SM',
241
242   # U+0A40 GURMUKHI VOWEL SIGN II may be preceded by U+0A02 GURMUKHI SIGN BINDI.
243   0x0A40: 'MPst',
244
245   # The following act like consonants.
246   0x0A72: 'C',
247   0x0A73: 'C',
248   0x1CF5: 'C',
249   0x1CF6: 'C',
250
251   # TODO: The following should only be allowed after a Visarga.
252   # For now, just treat them like regular tone marks.
253   0x1CE2: 'A',
254   0x1CE3: 'A',
255   0x1CE4: 'A',
256   0x1CE5: 'A',
257   0x1CE6: 'A',
258   0x1CE7: 'A',
259   0x1CE8: 'A',
260
261   # TODO: The following should only be allowed after some of
262   # the nasalization marks, maybe only for U+1CE9..U+1CF1.
263   # For now, just treat them like tone marks.
264   0x1CED: 'A',
265
266   # The following take marks in standalone clusters, similar to Avagraha.
267   0xA8F2: 'Symbol',
268   0xA8F3: 'Symbol',
269   0xA8F4: 'Symbol',
270   0xA8F5: 'Symbol',
271   0xA8F6: 'Symbol',
272   0xA8F7: 'Symbol',
273   0x1CE9: 'Symbol',
274   0x1CEA: 'Symbol',
275   0x1CEB: 'Symbol',
276   0x1CEC: 'Symbol',
277   0x1CEE: 'Symbol',
278   0x1CEF: 'Symbol',
279   0x1CF0: 'Symbol',
280   0x1CF1: 'Symbol',
281
282   0x0A51: 'M', # https://github.com/harfbuzz/harfbuzz/issues/524
283
284   # According to ScriptExtensions.txt, these Grantha marks may also be used in Tamil,
285   # so the Indic shaper needs to know their categories.
286   0x11301: 'SM',
287   0x11302: 'SM',
288   0x11303: 'SM',
289   0x1133B: 'N',
290   0x1133C: 'N',
291
292   0x0AFB: 'N', # https://github.com/harfbuzz/harfbuzz/issues/552
293   0x0B55: 'N', # https://github.com/harfbuzz/harfbuzz/issues/2849
294
295   0x09FC: 'PLACEHOLDER', # https://github.com/harfbuzz/harfbuzz/pull/1613
296   0x0C80: 'PLACEHOLDER', # https://github.com/harfbuzz/harfbuzz/pull/623
297   0x0D04: 'PLACEHOLDER', # https://github.com/harfbuzz/harfbuzz/pull/3511
298
299   0x25CC: 'DOTTEDCIRCLE',
300
301
302   # Khmer
303
304   0x179A: 'Ra',
305
306   0x17CC: 'Robatic',
307   0x17C9: 'Robatic',
308   0x17CA: 'Robatic',
309
310   0x17C6: 'Xgroup',
311   0x17CB: 'Xgroup',
312   0x17CD: 'Xgroup',
313   0x17CE: 'Xgroup',
314   0x17CF: 'Xgroup',
315   0x17D0: 'Xgroup',
316   0x17D1: 'Xgroup',
317
318   0x17C7: 'Ygroup',
319   0x17C8: 'Ygroup',
320   0x17DD: 'Ygroup',
321   0x17D3: 'Ygroup', # Just guessing. Uniscribe doesn't categorize it.
322
323   0x17D9: 'PLACEHOLDER', # https://github.com/harfbuzz/harfbuzz/issues/2384
324
325
326   # Myanmar
327
328   # https://docs.microsoft.com/en-us/typography/script-development/myanmar#analyze
329
330   0x104E: 'C', # The spec says C, IndicSyllableCategory says Consonant_Placeholder
331
332   0x1004: 'Ra',
333   0x101B: 'Ra',
334   0x105A: 'Ra',
335
336   0x1032: 'A',
337   0x1036: 'A',
338
339   0x103A: 'As',
340
341   #0x1040: 'D0', # XXX The spec says D0, but Uniscribe doesn't seem to do.
342
343   0x103E: 'MH',
344   0x1060: 'ML',
345   0x103C: 'MR',
346   0x103D: 'MW',
347   0x1082: 'MW',
348   0x103B: 'MY',
349   0x105E: 'MY',
350   0x105F: 'MY',
351
352   0x1063: 'PT',
353   0x1064: 'PT',
354   0x1069: 'PT',
355   0x106A: 'PT',
356   0x106B: 'PT',
357   0x106C: 'PT',
358   0x106D: 'PT',
359   0xAA7B: 'PT',
360
361   0x1038: 'SM',
362   0x1087: 'SM',
363   0x1088: 'SM',
364   0x1089: 'SM',
365   0x108A: 'SM',
366   0x108B: 'SM',
367   0x108C: 'SM',
368   0x108D: 'SM',
369   0x108F: 'SM',
370   0x109A: 'SM',
371   0x109B: 'SM',
372   0x109C: 'SM',
373
374   0x104A: 'PLACEHOLDER',
375 }
376 position_overrides = {
377
378   0x0A51: 'BELOW_C', # https://github.com/harfbuzz/harfbuzz/issues/524
379
380   0x0B01: 'BEFORE_SUB', # Oriya Bindu is BeforeSub in the spec.
381 }
382
383 def matra_pos_left(u, block):
384   return "PRE_M"
385 def matra_pos_right(u, block):
386   if block == 'Devanagari':     return  'AFTER_SUB'
387   if block == 'Bengali':        return  'AFTER_POST'
388   if block == 'Gurmukhi':       return  'AFTER_POST'
389   if block == 'Gujarati':       return  'AFTER_POST'
390   if block == 'Oriya':          return  'AFTER_POST'
391   if block == 'Tamil':          return  'AFTER_POST'
392   if block == 'Telugu':         return  'BEFORE_SUB' if u <= 0x0C42 else 'AFTER_SUB'
393   if block == 'Kannada':        return  'BEFORE_SUB' if u < 0x0CC3 or u > 0x0CD6 else 'AFTER_SUB'
394   if block == 'Malayalam':      return  'AFTER_POST'
395   return 'AFTER_SUB'
396 def matra_pos_top(u, block):
397   # BENG and MLYM don't have top matras.
398   if block == 'Devanagari':     return  'AFTER_SUB'
399   if block == 'Gurmukhi':       return  'AFTER_POST' # Deviate from spec
400   if block == 'Gujarati':       return  'AFTER_SUB'
401   if block == 'Oriya':          return  'AFTER_MAIN'
402   if block == 'Tamil':          return  'AFTER_SUB'
403   if block == 'Telugu':         return  'BEFORE_SUB'
404   if block == 'Kannada':        return  'BEFORE_SUB'
405   return 'AFTER_SUB'
406 def matra_pos_bottom(u, block):
407   if block == 'Devanagari':     return  'AFTER_SUB'
408   if block == 'Bengali':        return  'AFTER_SUB'
409   if block == 'Gurmukhi':       return  'AFTER_POST'
410   if block == 'Gujarati':       return  'AFTER_POST'
411   if block == 'Oriya':          return  'AFTER_SUB'
412   if block == 'Tamil':          return  'AFTER_POST'
413   if block == 'Telugu':         return  'BEFORE_SUB'
414   if block == 'Kannada':        return  'BEFORE_SUB'
415   if block == 'Malayalam':      return  'AFTER_POST'
416   return "AFTER_SUB"
417 def indic_matra_position(u, pos, block): # Reposition matra
418   if pos == 'PRE_C':    return matra_pos_left(u, block)
419   if pos == 'POST_C':   return matra_pos_right(u, block)
420   if pos == 'ABOVE_C':  return matra_pos_top(u, block)
421   if pos == 'BELOW_C':  return matra_pos_bottom(u, block)
422   assert (False)
423
424 def position_to_category(pos):
425   if pos == 'PRE_C':    return 'VPre'
426   if pos == 'ABOVE_C':  return 'VAbv'
427   if pos == 'BELOW_C':  return 'VBlw'
428   if pos == 'POST_C':   return 'VPst'
429   assert(False)
430
431
432 defaults = (category_map[defaults[0]], position_map[defaults[1]], defaults[2])
433
434 indic_data = {}
435 for k, (cat, pos, block) in combined.items():
436   cat = category_map[cat]
437   pos = position_map[pos]
438   indic_data[k] = (cat, pos, block)
439
440 for k,new_cat in category_overrides.items():
441   (cat, pos, _) = indic_data.get(k, defaults)
442   indic_data[k] = (new_cat, pos, unicode_data[2][k])
443
444 # We only expect position for certain types
445 positioned_categories = ('CM', 'SM', 'RS', 'H', 'M', 'MPst')
446 for k, (cat, pos, block) in indic_data.items():
447   if cat not in positioned_categories:
448     pos = 'END'
449     indic_data[k] = (cat, pos, block)
450
451 # Position overrides are more complicated
452
453 # Keep in sync with CONSONANT_FLAGS in the shaper
454 consonant_categories = ('C', 'CS', 'Ra','CM', 'V', 'PLACEHOLDER', 'DOTTEDCIRCLE')
455 matra_categories = ('M', 'MPst')
456 smvd_categories = ('SM', 'VD', 'A', 'Symbol')
457 for k, (cat, pos, block) in indic_data.items():
458   if cat in consonant_categories:
459     pos = 'BASE_C'
460   elif cat in matra_categories:
461     if block.startswith('Khmer') or block.startswith('Myanmar'):
462       cat = position_to_category(pos)
463     else:
464       pos = indic_matra_position(k, pos, block)
465   elif cat in smvd_categories:
466     pos = 'SMVD';
467   indic_data[k] = (cat, pos, block)
468
469 for k,new_pos in position_overrides.items():
470   (cat, pos, _) = indic_data.get(k, defaults)
471   indic_data[k] = (cat, new_pos, unicode_data[2][k])
472
473
474 values = [{_: 1} for _ in defaults]
475 for vv in indic_data.values():
476   for i,v in enumerate(vv):
477     values[i][v] = values[i].get (v, 0) + 1
478
479
480
481
482 # Move the outliers NO-BREAK SPACE and DOTTED CIRCLE out
483 singles = {}
484 for u in ALLOWED_SINGLES:
485         singles[u] = indic_data[u]
486         del indic_data[u]
487
488 print ("/* == Start of generated table == */")
489 print ("/*")
490 print (" * The following table is generated by running:")
491 print (" *")
492 print (" *   ./gen-indic-table.py IndicSyllabicCategory.txt IndicPositionalCategory.txt Blocks.txt")
493 print (" *")
494 print (" * on files with these headers:")
495 print (" *")
496 for h in headers:
497         for l in h:
498                 print (" * %s" % (l.strip()))
499 print (" */")
500 print ()
501 print ('#include "hb.hh"')
502 print ()
503 print ('#ifndef HB_NO_OT_SHAPE')
504 print ()
505 print ('#include "hb-ot-shaper-indic.hh"')
506 print ()
507 print ('#pragma GCC diagnostic push')
508 print ('#pragma GCC diagnostic ignored "-Wunused-macros"')
509 print ()
510
511 # Print categories
512 for shaper in categories:
513   print ('#include "hb-ot-shaper-%s-machine.hh"' % shaper)
514 print ()
515 done = {}
516 for shaper, shaper_cats in categories.items():
517   print ('/* %s */' % shaper)
518   for cat in shaper_cats:
519     v = shaper[0].upper()
520     if cat not in done:
521       print ("#define OT_%s %s_Cat(%s)" % (cat, v, cat))
522       done[cat] = v
523     else:
524       print ('static_assert (OT_%s == %s_Cat(%s), "");' % (cat, v, cat))
525 print ()
526
527 # Shorten values
528 short = [{
529         "Repha":                'Rf',
530         "PLACEHOLDER":          'GB',
531         "DOTTEDCIRCLE":         'DC',
532         "VPst":                 'VR',
533         "VPre":                 'VL',
534         "Robatic":              'Rt',
535         "Xgroup":               'Xg',
536         "Ygroup":               'Yg',
537         "As":                   'As',
538 },{
539         "END":                  'X',
540         "BASE_C":               'C',
541         "ABOVE_C":              'T',
542         "BELOW_C":              'B',
543         "POST_C":               'R',
544         "PRE_C":                'L',
545         "PRE_M":                'LM',
546         "AFTER_MAIN":           'A',
547         "AFTER_SUB":            'AS',
548         "BEFORE_SUB":           'BS',
549         "AFTER_POST":           'AP',
550         "SMVD":                 'SM',
551 }]
552 all_shorts = [{},{}]
553
554 # Add some of the values, to make them more readable, and to avoid duplicates
555
556 for i in range (2):
557         for v,s in short[i].items ():
558                 all_shorts[i][s] = v
559
560 what = ["OT", "POS"]
561 what_short = ["_OT", "_POS"]
562 cat_defs = []
563 for i in range (2):
564         vv = sorted (values[i].keys ())
565         for v in vv:
566                 v_no_and = v.replace ('_And_', '_')
567                 if v in short[i]:
568                         s = short[i][v]
569                 else:
570                         s = ''.join ([c for c in v_no_and if ord ('A') <= ord (c) <= ord ('Z')])
571                         if s in all_shorts[i]:
572                                 raise Exception ("Duplicate short value alias", v, all_shorts[i][s])
573                         all_shorts[i][s] = v
574                         short[i][v] = s
575                 cat_defs.append ((what_short[i] + '_' + s, what[i] + '_' + (v.upper () if i else v), str (values[i][v]), v))
576
577 maxlen_s = max ([len (c[0]) for c in cat_defs])
578 maxlen_l = max ([len (c[1]) for c in cat_defs])
579 maxlen_n = max ([len (c[2]) for c in cat_defs])
580 for s in what_short:
581         print ()
582         for c in [c for c in cat_defs if s in c[0]]:
583                 print ("#define %s %s /* %s chars; %s */" %
584                         (c[0].ljust (maxlen_s), c[1].ljust (maxlen_l), c[2].rjust (maxlen_n), c[3]))
585 print ()
586 print ('#pragma GCC diagnostic pop')
587 print ()
588 print ("#define INDIC_COMBINE_CATEGORIES(S,M) ((S) | ((M) << 8))")
589 print ()
590 print ("#define _(S,M) INDIC_COMBINE_CATEGORIES (%s_##S, %s_##M)" % tuple(what_short))
591 print ()
592 print ()
593
594 total = 0
595 used = 0
596 last_block = None
597 def print_block (block, start, end, data):
598         global total, used, last_block
599         if block and block != last_block:
600                 print ()
601                 print ()
602                 print ("  /* %s */" % block)
603         num = 0
604         assert start % 8 == 0
605         assert (end+1) % 8 == 0
606         for u in range (start, end+1):
607                 if u % 8 == 0:
608                         print ()
609                         print ("  /* %04X */" % u, end="")
610                 if u in data:
611                         num += 1
612                 d = data.get (u, defaults)
613                 print ("%9s" % ("_(%s,%s)," % (short[0][d[0]], short[1][d[1]])), end="")
614
615         total += end - start + 1
616         used += num
617         if block:
618                 last_block = block
619
620 uu = sorted (indic_data)
621
622 last = -100000
623 num = 0
624 offset = 0
625 starts = []
626 ends = []
627 print ("static const uint16_t indic_table[] = {")
628 for u in uu:
629         if u <= last:
630                 continue
631         block = indic_data[u][2]
632
633         start = u//8*8
634         end = start+1
635         while end in uu and block == indic_data[end][2]:
636                 end += 1
637         end = (end-1)//8*8 + 7
638
639         if start != last + 1:
640                 if start - last <= 1+16*2:
641                         print_block (None, last+1, start-1, indic_data)
642                 else:
643                         if last >= 0:
644                                 ends.append (last + 1)
645                                 offset += ends[-1] - starts[-1]
646                         print ()
647                         print ()
648                         print ("#define indic_offset_0x%04xu %d" % (start, offset))
649                         starts.append (start)
650
651         print_block (block, start, end, indic_data)
652         last = end
653 ends.append (last + 1)
654 offset += ends[-1] - starts[-1]
655 print ()
656 print ()
657 occupancy = used * 100. / total
658 page_bits = 12
659 print ("}; /* Table items: %d; occupancy: %d%% */" % (offset, occupancy))
660 print ()
661 print ("uint16_t")
662 print ("hb_indic_get_categories (hb_codepoint_t u)")
663 print ("{")
664 print ("  switch (u >> %d)" % page_bits)
665 print ("  {")
666 pages = set ([u>>page_bits for u in starts+ends+list (singles.keys ())])
667 for p in sorted(pages):
668         print ("    case 0x%0Xu:" % p)
669         for u,d in singles.items ():
670                 if p != u>>page_bits: continue
671                 print ("      if (unlikely (u == 0x%04Xu)) return _(%s,%s);" % (u, short[0][d[0]], short[1][d[1]]))
672         for (start,end) in zip (starts, ends):
673                 if p not in [start>>page_bits, end>>page_bits]: continue
674                 offset = "indic_offset_0x%04xu" % start
675                 print ("      if (hb_in_range<hb_codepoint_t> (u, 0x%04Xu, 0x%04Xu)) return indic_table[u - 0x%04Xu + %s];" % (start, end-1, start, offset))
676         print ("      break;")
677         print ("")
678 print ("    default:")
679 print ("      break;")
680 print ("  }")
681 print ("  return _(X,X);")
682 print ("}")
683 print ()
684 print ("#undef _")
685 print ("#undef INDIC_COMBINE_CATEGORIES")
686 for i in range (2):
687         print ()
688         vv = sorted (values[i].keys ())
689         for v in vv:
690                 print ("#undef %s_%s" %
691                         (what_short[i], short[i][v]))
692 print ()
693 print ('#endif')
694 print ()
695 print ("/* == End of generated table == */")
696
697 # Maintain at least 50% occupancy in the table */
698 if occupancy < 50:
699         raise Exception ("Table too sparse, please investigate: ", occupancy)