(root)/
harfbuzz-8.3.0/
src/
gen-ucd-table.py
       1  #!/usr/bin/env python3
       2  
       3  """usage: ./gen-ucd-table ucd.nounihan.grouped.xml [/path/to/hb-common.h]
       4  
       5  Input file:
       6  * https://unicode.org/Public/UCD/latest/ucdxml/ucd.nounihan.grouped.zip
       7  """
       8  
       9  import sys, re
      10  import logging
      11  logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO)
      12  
      13  if len (sys.argv) not in (2, 3):
      14  	sys.exit (__doc__)
      15  
      16  # https://github.com/harfbuzz/packtab
      17  import packTab
      18  import packTab.ucdxml
      19  
      20  logging.info('Loading UCDXML...')
      21  ucdxml = packTab.ucdxml.load_ucdxml(sys.argv[1])
      22  ucd = packTab.ucdxml.ucdxml_get_repertoire(ucdxml)
      23  
      24  hb_common_h = 'hb-common.h' if len (sys.argv) < 3 else sys.argv[2]
      25  
      26  logging.info('Preparing data tables...')
      27  
      28  
      29  # This is how the data is encoded:
      30  #
      31  # General_Category (gc), Canonical_Combining_Class (ccc),
      32  # and Script (sc) are encoded as integers.
      33  #
      34  # Mirroring character (bmg) is encoded as difference from
      35  # the original character.
      36  #
      37  # Composition & Decomposition (dm) are encoded elaborately,
      38  # as discussed below.
      39  
      40  gc = [u['gc'] for u in ucd]
      41  ccc = [int(u['ccc']) for u in ucd]
      42  bmg = [int(v, 16) - int(u) if v else 0 for u,v in enumerate(u['bmg'] for u in ucd)]
      43  sc = [u['sc'] for u in ucd]
      44  
      45  
      46  # Prepare Compose / Decompose data
      47  #
      48  # This code is very dense.  See hb_ucd_compose() / hb_ucd_decompose() for the logic.
      49  
      50  dm = {i:tuple(int(v, 16) for v in u['dm'].split()) for i,u in enumerate(ucd)
      51        if u['dm'] != '#' and u['dt'] == 'can' and not (0xAC00 <= i < 0xAC00+11172)}
      52  ce = {i for i,u in enumerate(ucd) if u['Comp_Ex'] == 'Y'}
      53  
      54  assert not any(v for v in dm.values() if len(v) not in (1,2))
      55  dm1 = sorted(set(v for v in dm.values() if len(v) == 1))
      56  assert all((v[0] >> 16) in (0,2) for v in dm1)
      57  dm1_p0_array = ['0x%04Xu' % (v[0] & 0xFFFF) for v in dm1 if (v[0] >> 16) == 0]
      58  dm1_p2_array = ['0x%04Xu' % (v[0] & 0xFFFF) for v in dm1 if (v[0] >> 16) == 2]
      59  dm1_order = {v:i+1 for i,v in enumerate(dm1)}
      60  
      61  dm2 = sorted((v+(i if i not in ce and not ccc[i] else 0,), v)
      62               for i,v in dm.items() if len(v) == 2)
      63  
      64  filt = lambda v: ((v[0] & 0xFFFFF800) == 0x0000 and
      65                    (v[1] & 0xFFFFFF80) == 0x0300 and
      66                    (v[2] & 0xFFF0C000) == 0x0000)
      67  dm2_u32_array = [v for v in dm2 if filt(v[0])]
      68  dm2_u64_array = [v for v in dm2 if not filt(v[0])]
      69  assert dm2_u32_array + dm2_u64_array == dm2
      70  dm2_u32_array = ["HB_CODEPOINT_ENCODE3_11_7_14 (0x%04Xu, 0x%04Xu, 0x%04Xu)" % v[0] for v in dm2_u32_array]
      71  dm2_u64_array = ["HB_CODEPOINT_ENCODE3 (0x%04Xu, 0x%04Xu, 0x%04Xu)" % v[0] for v in dm2_u64_array]
      72  
      73  l = 1 + len(dm1_p0_array) + len(dm1_p2_array)
      74  dm2_order = {v[1]:i+l for i,v in enumerate(dm2)}
      75  
      76  dm_order = {None: 0}
      77  dm_order.update(dm1_order)
      78  dm_order.update(dm2_order)
      79  
      80  
      81  # Prepare General_Category / Script mapping arrays
      82  
      83  gc_order = dict()
      84  for i,v in enumerate(('Cc', 'Cf', 'Cn', 'Co', 'Cs', 'Ll', 'Lm', 'Lo', 'Lt', 'Lu',
      85                        'Mc', 'Me', 'Mn', 'Nd', 'Nl', 'No', 'Pc', 'Pd', 'Pe', 'Pf',
      86                        'Pi', 'Po', 'Ps', 'Sc', 'Sk', 'Sm', 'So', 'Zl', 'Zp', 'Zs',)):
      87      gc_order[i] = v
      88      gc_order[v] = i
      89  
      90  sc_order = dict()
      91  sc_array = []
      92  sc_re = re.compile(r"\b(HB_SCRIPT_[_A-Z]*).*HB_TAG [(]'(.)','(.)','(.)','(.)'[)]")
      93  for line in open(hb_common_h):
      94      m = sc_re.search (line)
      95      if not m: continue
      96      name = m.group(1)
      97      tag = ''.join(m.group(i) for i in range(2, 6))
      98      i = len(sc_array)
      99      sc_order[tag] = i
     100      sc_order[i] = tag
     101      sc_array.append(name)
     102  
     103  
     104  # Write out main data
     105  
     106  DEFAULT = 'DEFAULT'
     107  COMPACT = 'COMPACT'
     108  SLOPPY  = 'SLOPPY'
     109  
     110  compression_level = {
     111      DEFAULT: 5,
     112      COMPACT: 9,
     113      SLOPPY:  9,
     114  }
     115  
     116  logging.info('Generating output...')
     117  print("/* == Start of generated table == */")
     118  print("/*")
     119  print(" * The following table is generated by running:")
     120  print(" *")
     121  print(" *   ./gen-ucd-table.py ucd.nounihan.grouped.xml")
     122  print(" *")
     123  print(" * on file with this description:", ucdxml.description)
     124  print(" */")
     125  print()
     126  print("#ifndef HB_UCD_TABLE_HH")
     127  print("#define HB_UCD_TABLE_HH")
     128  print()
     129  print('#include "hb.hh"')
     130  print()
     131  
     132  
     133  # Write mapping data
     134  
     135  code = packTab.Code('_hb_ucd')
     136  sc_array, _ = code.addArray('hb_script_t', 'sc_map', sc_array)
     137  dm1_p0_array, _ = code.addArray('uint16_t', 'dm1_p0_map', dm1_p0_array)
     138  dm1_p2_array, _ = code.addArray('uint16_t', 'dm1_p2_map', dm1_p2_array)
     139  dm2_u32_array, _ = code.addArray('uint32_t', 'dm2_u32_map', dm2_u32_array)
     140  dm2_u64_array, _ = code.addArray('uint64_t', 'dm2_u64_map', dm2_u64_array)
     141  code.print_c(linkage='static inline')
     142  
     143  datasets = [
     144      ('gc', gc, 'Cn', gc_order),
     145      ('ccc', ccc, 0, None),
     146      ('bmg', bmg, 0, None),
     147      ('sc', sc, 'Zzzz', sc_order),
     148      ('dm', dm, None, dm_order),
     149  ]
     150  
     151  
     152  # Write main data
     153  
     154  for step in (DEFAULT, COMPACT, SLOPPY):
     155      compression = compression_level[step]
     156      logging.info('  Compression=%d:' % compression)
     157      print()
     158      if step == DEFAULT:
     159          print('#ifndef HB_OPTIMIZE_SIZE')
     160      elif step == COMPACT:
     161          print('#elif !defined(HB_NO_UCD_UNASSIGNED)')
     162      elif step == SLOPPY:
     163          print('#else')
     164      else:
     165          assert False
     166      print()
     167  
     168      if step == SLOPPY:
     169          for i in range(len(gc)):
     170              if (i % 128) and gc[i] == 'Cn':
     171                  gc[i] = gc[i - 1]
     172          for i in range(len(gc) - 2, -1, -1):
     173              if ((i + 1) % 128) and gc[i] == 'Cn':
     174                  gc[i] = gc[i + 1]
     175          for i in range(len(sc)):
     176              if (i % 128) and sc[i] == 'Zzzz':
     177                  sc[i] = sc[i - 1]
     178          for i in range(len(sc) - 2, -1, -1):
     179              if ((i + 1) % 128) and sc[i] == 'Zzzz':
     180                  sc[i] = sc[i + 1]
     181  
     182  
     183      code = packTab.Code('_hb_ucd')
     184  
     185      for name,data,default,mapping in datasets:
     186          sol = packTab.pack_table(data, default, mapping=mapping, compression=compression)
     187          logging.info('      Dataset=%-8s FullCost=%d' % (name, sol.fullCost))
     188          sol.genCode(code, name)
     189  
     190      code.print_c(linkage='static inline')
     191  
     192      print()
     193  
     194  
     195  print('#endif')
     196  print()
     197  
     198  print()
     199  print("#endif /* HB_UCD_TABLE_HH */")
     200  print()
     201  print("/* == End of generated table == */")
     202  logging.info('Done.')