gen-arabic-table.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. #!/usr/bin/env python3
  2. """usage: ./gen-arabic-table.py ArabicShaping.txt UnicodeData.txt Blocks.txt
  3. Input files:
  4. * https://unicode.org/Public/UCD/latest/ucd/ArabicShaping.txt
  5. * https://unicode.org/Public/UCD/latest/ucd/UnicodeData.txt
  6. * https://unicode.org/Public/UCD/latest/ucd/Blocks.txt
  7. """
  8. import os.path, sys
  9. if len (sys.argv) != 4:
  10. sys.exit (__doc__)
  11. files = [open (x, encoding='utf-8') for x in sys.argv[1:]]
  12. headers = [[files[0].readline (), files[0].readline ()], [files[2].readline (), files[2].readline ()]]
  13. headers.append (["UnicodeData.txt does not have a header."])
  14. while files[0].readline ().find ('##################') < 0:
  15. pass
  16. blocks = {}
  17. def read_blocks(f):
  18. global blocks
  19. for line in f:
  20. j = line.find ('#')
  21. if j >= 0:
  22. line = line[:j]
  23. fields = [x.strip () for x in line.split (';')]
  24. if len (fields) == 1:
  25. continue
  26. uu = fields[0].split ('..')
  27. start = int (uu[0], 16)
  28. if len (uu) == 1:
  29. end = start
  30. else:
  31. end = int (uu[1], 16)
  32. t = fields[1]
  33. for u in range (start, end + 1):
  34. blocks[u] = t
  35. def print_joining_table(f):
  36. values = {}
  37. for line in f:
  38. if line[0] == '#':
  39. continue
  40. fields = [x.strip () for x in line.split (';')]
  41. if len (fields) == 1:
  42. continue
  43. u = int (fields[0], 16)
  44. if fields[3] in ["ALAPH", "DALATH RISH"]:
  45. value = "JOINING_GROUP_" + fields[3].replace(' ', '_')
  46. else:
  47. value = "JOINING_TYPE_" + fields[2]
  48. values[u] = value
  49. short_value = {}
  50. for value in sorted (set ([v for v in values.values ()] + ['JOINING_TYPE_X'])):
  51. short = ''.join(x[0] for x in value.split('_')[2:])
  52. assert short not in short_value.values()
  53. short_value[value] = short
  54. print ()
  55. for value,short in short_value.items():
  56. print ("#define %s %s" % (short, value))
  57. uu = sorted(values.keys())
  58. num = len(values)
  59. all_blocks = set([blocks[u] for u in uu])
  60. last = -100000
  61. ranges = []
  62. for u in uu:
  63. if u - last <= 1+16*5:
  64. ranges[-1][-1] = u
  65. else:
  66. ranges.append([u,u])
  67. last = u
  68. print ()
  69. print ("static const uint8_t joining_table[] =")
  70. print ("{")
  71. last_block = None
  72. offset = 0
  73. for start,end in ranges:
  74. print ()
  75. print ("#define joining_offset_0x%04xu %d" % (start, offset))
  76. for u in range(start, end+1):
  77. block = blocks.get(u, last_block)
  78. value = values.get(u, "JOINING_TYPE_X")
  79. if block != last_block or u == start:
  80. if u != start:
  81. print ()
  82. if block in all_blocks:
  83. print ("\n /* %s */" % block)
  84. else:
  85. print ("\n /* FILLER */")
  86. last_block = block
  87. if u % 32 != 0:
  88. print ()
  89. print (" /* %04X */" % (u//32*32), " " * (u % 32), end="")
  90. if u % 32 == 0:
  91. print ()
  92. print (" /* %04X */ " % u, end="")
  93. print ("%s," % short_value[value], end="")
  94. print ()
  95. offset += end - start + 1
  96. print ()
  97. occupancy = num * 100. / offset
  98. print ("}; /* Table items: %d; occupancy: %d%% */" % (offset, occupancy))
  99. print ()
  100. page_bits = 12
  101. print ()
  102. print ("static unsigned int")
  103. print ("joining_type (hb_codepoint_t u)")
  104. print ("{")
  105. print (" switch (u >> %d)" % page_bits)
  106. print (" {")
  107. pages = set([u>>page_bits for u in [s for s,e in ranges]+[e for s,e in ranges]])
  108. for p in sorted(pages):
  109. print (" case 0x%0Xu:" % p)
  110. for (start,end) in ranges:
  111. if p not in [start>>page_bits, end>>page_bits]: continue
  112. offset = "joining_offset_0x%04xu" % start
  113. print (" if (hb_in_range<hb_codepoint_t> (u, 0x%04Xu, 0x%04Xu)) return joining_table[u - 0x%04Xu + %s];" % (start, end, start, offset))
  114. print (" break;")
  115. print ("")
  116. print (" default:")
  117. print (" break;")
  118. print (" }")
  119. print (" return X;")
  120. print ("}")
  121. print ()
  122. for value,short in short_value.items():
  123. print ("#undef %s" % (short))
  124. print ()
  125. LIGATURES = (
  126. 0xF2EE, 0xFC08, 0xFC0E, 0xFC12, 0xFC32, 0xFC3F, 0xFC40, 0xFC41, 0xFC42,
  127. 0xFC44, 0xFC4E, 0xFC5E, 0xFC60, 0xFC61, 0xFC62, 0xFC6A, 0xFC6D, 0xFC6F,
  128. 0xFC70, 0xFC73, 0xFC75, 0xFC86, 0xFC8F, 0xFC91, 0xFC94, 0xFC9C, 0xFC9D,
  129. 0xFC9E, 0xFC9F, 0xFCA1, 0xFCA2, 0xFCA3, 0xFCA4, 0xFCA8, 0xFCAA, 0xFCAC,
  130. 0xFCB0, 0xFCC9, 0xFCCA, 0xFCCB, 0xFCCC, 0xFCCD, 0xFCCE, 0xFCCF, 0xFCD0,
  131. 0xFCD1, 0xFCD2, 0xFCD3, 0xFCD5, 0xFCDA, 0xFCDB, 0xFCDC, 0xFCDD, 0xFD30,
  132. 0xFD88, 0xFEF5, 0xFEF6, 0xFEF7, 0xFEF8, 0xFEF9, 0xFEFA, 0xFEFB, 0xFEFC,
  133. 0xF201, 0xF211, 0xF2EE,
  134. )
  135. def print_shaping_table(f):
  136. shapes = {}
  137. ligatures = {}
  138. names = {}
  139. lines = f.readlines()
  140. lines += [
  141. "F201;PUA ARABIC LIGATURE LELLAH ISOLATED FORM;Lo;0;AL;<isolated> 0644 0644 0647;;;;N;;;;;",
  142. "F211;PUA ARABIC LIGATURE LAM WITH MEEM WITH JEEM INITIAL FORM;Lo;0;AL;<initial> 0644 0645 062C;;;;N;;;;;",
  143. "F2EE;PUA ARABIC LIGATURE SHADDA WITH FATHATAN ISOLATED FORM;Lo;0;AL;<isolated> 0020 064B 0651;;;;N;;;;;",
  144. ]
  145. for line in lines:
  146. fields = [x.strip () for x in line.split (';')]
  147. if fields[5][0:1] != '<':
  148. continue
  149. items = fields[5].split (' ')
  150. shape, items = items[0][1:-1], tuple (int (x, 16) for x in items[1:])
  151. c = int (fields[0], 16)
  152. if not shape in ['initial', 'medial', 'isolated', 'final']:
  153. continue
  154. if len (items) != 1:
  155. # Mark ligatures start with space and are in visual order, so we
  156. # remove the space and reverse the items.
  157. if items[0] == 0x0020:
  158. items = items[:0:-1]
  159. shape = None
  160. # We only care about a subset of ligatures
  161. if c not in LIGATURES:
  162. continue
  163. # Save ligature
  164. names[c] = fields[1]
  165. if items not in ligatures:
  166. ligatures[items] = {}
  167. ligatures[items][shape] = c
  168. else:
  169. # Save shape
  170. if items[0] not in names:
  171. names[items[0]] = fields[1]
  172. else:
  173. names[items[0]] = os.path.commonprefix ([names[items[0]], fields[1]]).strip ()
  174. if items[0] not in shapes:
  175. shapes[items[0]] = {}
  176. shapes[items[0]][shape] = c
  177. print ()
  178. print ("static const uint16_t shaping_table[][4] =")
  179. print ("{")
  180. keys = shapes.keys ()
  181. min_u, max_u = min (keys), max (keys)
  182. for u in range (min_u, max_u + 1):
  183. s = [shapes[u][shape] if u in shapes and shape in shapes[u] else 0
  184. for shape in ['initial', 'medial', 'final', 'isolated']]
  185. value = ', '.join ("0x%04Xu" % c for c in s)
  186. print (" {%s}, /* U+%04X %s */" % (value, u, names[u] if u in names else ""))
  187. print ("};")
  188. print ()
  189. print ("#define SHAPING_TABLE_FIRST 0x%04Xu" % min_u)
  190. print ("#define SHAPING_TABLE_LAST 0x%04Xu" % max_u)
  191. print ()
  192. ligas_2 = {}
  193. ligas_3 = {}
  194. ligas_mark_2 = {}
  195. for key in ligatures.keys ():
  196. for shape in ligatures[key]:
  197. c = ligatures[key][shape]
  198. if len(key) == 3:
  199. if shape == 'isolated':
  200. liga = (shapes[key[0]]['initial'], shapes[key[1]]['medial'], shapes[key[2]]['final'])
  201. elif shape == 'final':
  202. liga = (shapes[key[0]]['medial'], shapes[key[1]]['medial'], shapes[key[2]]['final'])
  203. elif shape == 'initial':
  204. liga = (shapes[key[0]]['initial'], shapes[key[1]]['medial'], shapes[key[2]]['medial'])
  205. else:
  206. raise Exception ("Unexpected shape", shape)
  207. if liga[0] not in ligas_3:
  208. ligas_3[liga[0]] = []
  209. ligas_3[liga[0]].append ((liga[1], liga[2], c))
  210. elif len(key) == 2:
  211. if shape is None:
  212. liga = key
  213. if liga[0] not in ligas_mark_2:
  214. ligas_mark_2[liga[0]] = []
  215. ligas_mark_2[liga[0]].append ((liga[1], c))
  216. continue
  217. elif shape == 'isolated':
  218. liga = (shapes[key[0]]['initial'], shapes[key[1]]['final'])
  219. elif shape == 'final':
  220. liga = (shapes[key[0]]['medial'], shapes[key[1]]['final'])
  221. elif shape == 'initial':
  222. liga = (shapes[key[0]]['initial'], shapes[key[1]]['medial'])
  223. else:
  224. raise Exception ("Unexpected shape", shape)
  225. if liga[0] not in ligas_2:
  226. ligas_2[liga[0]] = []
  227. ligas_2[liga[0]].append ((liga[1], c))
  228. else:
  229. raise Exception ("Unexpected number of ligature components", key)
  230. max_i = max (len (ligas_2[l]) for l in ligas_2)
  231. print ()
  232. print ("static const struct ligature_set_t {")
  233. print (" uint16_t first;")
  234. print (" struct ligature_pairs_t {")
  235. print (" uint16_t components[1];")
  236. print (" uint16_t ligature;")
  237. print (" } ligatures[%d];" % max_i)
  238. print ("} ligature_table[] =")
  239. print ("{")
  240. for first in sorted (ligas_2.keys ()):
  241. print (" { 0x%04Xu, {" % (first))
  242. for liga in ligas_2[first]:
  243. print (" { {0x%04Xu}, 0x%04Xu }, /* %s */" % (liga[0], liga[1], names[liga[1]]))
  244. print (" }},")
  245. print ("};")
  246. print ()
  247. max_i = max (len (ligas_mark_2[l]) for l in ligas_mark_2)
  248. print ()
  249. print ("static const struct ligature_mark_set_t {")
  250. print (" uint16_t first;")
  251. print (" struct ligature_pairs_t {")
  252. print (" uint16_t components[1];")
  253. print (" uint16_t ligature;")
  254. print (" } ligatures[%d];" % max_i)
  255. print ("} ligature_mark_table[] =")
  256. print ("{")
  257. for first in sorted (ligas_mark_2.keys ()):
  258. print (" { 0x%04Xu, {" % (first))
  259. for liga in ligas_mark_2[first]:
  260. print (" { {0x%04Xu}, 0x%04Xu }, /* %s */" % (liga[0], liga[1], names[liga[1]]))
  261. print (" }},")
  262. print ("};")
  263. print ()
  264. max_i = max (len (ligas_3[l]) for l in ligas_3)
  265. print ()
  266. print ("static const struct ligature_3_set_t {")
  267. print (" uint16_t first;")
  268. print (" struct ligature_triplets_t {")
  269. print (" uint16_t components[2];")
  270. print (" uint16_t ligature;")
  271. print (" } ligatures[%d];" % max_i)
  272. print ("} ligature_3_table[] =")
  273. print ("{")
  274. for first in sorted (ligas_3.keys ()):
  275. print (" { 0x%04Xu, {" % (first))
  276. for liga in ligas_3[first]:
  277. print (" { {0x%04Xu, 0x%04Xu}, 0x%04Xu}, /* %s */" % (liga[0], liga[1], liga[2], names[liga[2]]))
  278. print (" }},")
  279. print ("};")
  280. print ()
  281. print ("/* == Start of generated table == */")
  282. print ("/*")
  283. print (" * The following table is generated by running:")
  284. print (" *")
  285. print (" * ./gen-arabic-table.py ArabicShaping.txt UnicodeData.txt Blocks.txt")
  286. print (" *")
  287. print (" * on files with these headers:")
  288. print (" *")
  289. for h in headers:
  290. for l in h:
  291. print (" * %s" % (l.strip()))
  292. print (" */")
  293. print ()
  294. print ("#ifndef HB_OT_SHAPER_ARABIC_TABLE_HH")
  295. print ("#define HB_OT_SHAPER_ARABIC_TABLE_HH")
  296. print ()
  297. read_blocks (files[2])
  298. print_joining_table (files[0])
  299. print_shaping_table (files[1])
  300. print ()
  301. print ("#endif /* HB_OT_SHAPER_ARABIC_TABLE_HH */")
  302. print ()
  303. print ("/* == End of generated table == */")