[ot] Generate Hebrew PUA mapping from data

Keep the legacy Hebrew encoding in a data file and generate the
lookup table with packTab, matching the existing Arabic PUA workflow.

Testing: meson test -C build --print-errorlogs

Assisted-by: OpenAI Codex <noreply@openai.com>
diff --git a/src/HebrewPUA.txt b/src/HebrewPUA.txt
new file mode 100644
index 0000000..6f78309
--- /dev/null
+++ b/src/HebrewPUA.txt
@@ -0,0 +1,121 @@
+#
+#    Name:     Legacy Hebrew encoding
+#
+#    Format: Three tab-separated columns
+#        Column #1 is the PUA code (in hex as 0xXXXX)
+#        Column #2 is the Unicode (in hex as 0xXXXX)
+#        Column #3 is the Unicode name (follows a comment sign, '#')
+#
+#    The entries are in PUA order
+#
+0xF020	0x0020	# SPACE
+0xF021	0x0021	# EXCLAMATION MARK
+0xF022	0x0022	# QUOTATION MARK
+0xF023	0x0023	# NUMBER SIGN
+0xF024	0x0024	# DOLLAR SIGN
+0xF025	0x0025	# PERCENT SIGN
+0xF026	0x0026	# AMPERSAND
+0xF027	0x0027	# APOSTROPHE
+0xF028	0x0028	# LEFT PARENTHESIS
+0xF029	0x0029	# RIGHT PARENTHESIS
+0xF02A	0x002A	# ASTERISK
+0xF02B	0x002B	# PLUS SIGN
+0xF02C	0x002C	# COMMA
+0xF02D	0x002D	# HYPHEN-MINUS
+0xF02E	0x002E	# FULL STOP
+0xF02F	0x002F	# SOLIDUS
+0xF030	0x0030	# DIGIT ZERO
+0xF031	0x0031	# DIGIT ONE
+0xF032	0x0032	# DIGIT TWO
+0xF033	0x0033	# DIGIT THREE
+0xF034	0x0034	# DIGIT FOUR
+0xF035	0x0035	# DIGIT FIVE
+0xF036	0x0036	# DIGIT SIX
+0xF037	0x0037	# DIGIT SEVEN
+0xF038	0x0038	# DIGIT EIGHT
+0xF039	0x0039	# DIGIT NINE
+0xF03A	0x003A	# COLON
+0xF03B	0x003B	# SEMICOLON
+0xF03C	0x003C	# LESS-THAN SIGN
+0xF03D	0x003D	# EQUALS SIGN
+0xF03E	0x003E	# GREATER-THAN SIGN
+0xF03F	0x003F	# QUESTION MARK
+0xF040	0x0040	# COMMERCIAL AT
+0xF05B	0x005B	# LEFT SQUARE BRACKET
+0xF05C	0x005C	# REVERSE SOLIDUS
+0xF05D	0x005D	# RIGHT SQUARE BRACKET
+0xF05E	0x005E	# CIRCUMFLEX ACCENT
+0xF05F	0x005F	# LOW LINE
+0xF07B	0x007B	# LEFT CURLY BRACKET
+0xF07C	0x007C	# VERTICAL LINE
+0xF07D	0x007D	# RIGHT CURLY BRACKET
+0xF07E	0x007E	# TILDE
+0xF080	0x2070	# SUPERSCRIPT ZERO
+0xF081	0x00B9	# SUPERSCRIPT ONE
+0xF082	0x00B2	# SUPERSCRIPT TWO
+0xF083	0x00B3	# SUPERSCRIPT THREE
+0xF084	0x2074	# SUPERSCRIPT FOUR
+0xF085	0x2075	# SUPERSCRIPT FIVE
+0xF086	0x2076	# SUPERSCRIPT SIX
+0xF087	0x2077	# SUPERSCRIPT SEVEN
+0xF088	0x2078	# SUPERSCRIPT EIGHT
+0xF089	0x2079	# SUPERSCRIPT NINE
+0xF0A3	0x00A3	# POUND SIGN
+0xF0A4	0x20AA	# NEW SHEQEL SIGN
+0xF0A7	0x00A7	# SECTION SIGN
+0xF0AA	0x00D7	# MULTIPLICATION SIGN
+0xF0B0	0x00B0	# DEGREE SIGN
+0xF0B7	0x00B7	# MIDDLE DOT
+0xF0BA	0x00F7	# DIVISION SIGN
+0xF0BC	0x00BC	# VULGAR FRACTION ONE QUARTER
+0xF0BD	0x00BD	# VULGAR FRACTION ONE HALF
+0xF0BE	0x00BE	# VULGAR FRACTION THREE QUARTERS
+0xF0C0	0x05B0	# HEBREW POINT SHEVA
+0xF0C1	0x05B1	# HEBREW POINT HATAF SEGOL
+0xF0C2	0x05B2	# HEBREW POINT HATAF PATAH
+0xF0C3	0x05B3	# HEBREW POINT HATAF QAMATS
+0xF0C4	0x05B4	# HEBREW POINT HIRIQ
+0xF0C5	0x05B5	# HEBREW POINT TSERE
+0xF0C6	0x05B6	# HEBREW POINT SEGOL
+0xF0C7	0x05B7	# HEBREW POINT PATAH
+0xF0C8	0x05B8	# HEBREW POINT QAMATS
+0xF0C9	0x05B9	# HEBREW POINT HOLAM
+0xF0CA	0x05BA	# HEBREW POINT HOLAM HASER FOR VAV
+0xF0CB	0x05BB	# HEBREW POINT QUBUTS
+0xF0CC	0x05BC	# HEBREW POINT DAGESH OR MAPIQ
+0xF0CD	0x05BD	# HEBREW POINT METEG
+0xF0CE	0x05BE	# HEBREW PUNCTUATION MAQAF
+0xF0CF	0x05BF	# HEBREW POINT RAFE
+0xF0D0	0x05C0	# HEBREW PUNCTUATION PASEQ
+0xF0D1	0x05C1	# HEBREW POINT SHIN DOT
+0xF0D2	0x05C2	# HEBREW POINT SIN DOT
+0xF0D3	0x05C3	# HEBREW PUNCTUATION SOF PASUQ
+0xF0E0	0x05D0	# HEBREW LETTER ALEF
+0xF0E1	0x05D1	# HEBREW LETTER BET
+0xF0E2	0x05D2	# HEBREW LETTER GIMEL
+0xF0E3	0x05D3	# HEBREW LETTER DALET
+0xF0E4	0x05D4	# HEBREW LETTER HE
+0xF0E5	0x05D5	# HEBREW LETTER VAV
+0xF0E6	0x05D6	# HEBREW LETTER ZAYIN
+0xF0E7	0x05D7	# HEBREW LETTER HET
+0xF0E8	0x05D8	# HEBREW LETTER TET
+0xF0E9	0x05D9	# HEBREW LETTER YOD
+0xF0EA	0x05DA	# HEBREW LETTER FINAL KAF
+0xF0EB	0x05DB	# HEBREW LETTER KAF
+0xF0EC	0x05DC	# HEBREW LETTER LAMED
+0xF0ED	0x05DD	# HEBREW LETTER FINAL MEM
+0xF0EE	0x05DE	# HEBREW LETTER MEM
+0xF0EF	0x05DF	# HEBREW LETTER FINAL NUN
+0xF0F0	0x05E0	# HEBREW LETTER NUN
+0xF0F1	0x05E1	# HEBREW LETTER SAMEKH
+0xF0F2	0x05E2	# HEBREW LETTER AYIN
+0xF0F3	0x05E3	# HEBREW LETTER FINAL PE
+0xF0F4	0x05E4	# HEBREW LETTER PE
+0xF0F5	0x05E5	# HEBREW LETTER FINAL TSADI
+0xF0F6	0x05E6	# HEBREW LETTER TSADI
+0xF0F7	0x05E7	# HEBREW LETTER QOF
+0xF0F8	0x05E8	# HEBREW LETTER RESH
+0xF0F9	0x05E9	# HEBREW LETTER SHIN
+0xF0FA	0x05EA	# HEBREW LETTER TAV
+0xF0FD	0x200E	# LEFT-TO-RIGHT MARK
+0xF0FE	0x200F	# RIGHT-TO-LEFT MARK
diff --git a/src/gen-hebrew-pua.py b/src/gen-hebrew-pua.py
new file mode 100755
index 0000000..f4da2e5
--- /dev/null
+++ b/src/gen-hebrew-pua.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python3
+
+"""usage: ./gen-hebrew-pua.py
+"""
+
+import packTab
+
+
+print ("/* == Start of generated table == */")
+print ("/*")
+print (" * The following table is generated by running:")
+print (" *")
+print (" *   ./gen-hebrew-pua.py")
+print (" *")
+print (" */")
+print ()
+print ("#ifndef HB_OT_SHAPER_HEBREW_PUA_HH")
+print ("#define HB_OT_SHAPER_HEBREW_PUA_HH")
+print ()
+print ('#include "hb.hh"')
+print ()
+
+code = packTab.Code('_hb_hebrew_pua')
+
+with open ("HebrewPUA.txt", encoding='utf-8') as f:
+    fields = [l.split('\t') for l in f if l[:1] != '#']
+data = {int(fs[1], 16):int(fs[0], 16) for fs in fields}
+sol = packTab.pack_table(data, compression=9)
+sol.genCode(code, 'map')
+
+code.print_c()
+
+print ()
+print ("#endif /* HB_OT_SHAPER_HEBREW_PUA_HH */")
+print ()
+print ("/* == End of generated table == */")
diff --git a/src/hb-ot-shaper-hebrew-pua.hh b/src/hb-ot-shaper-hebrew-pua.hh
index a9e3b64..0d321e1 100644
--- a/src/hb-ot-shaper-hebrew-pua.hh
+++ b/src/hb-ot-shaper-hebrew-pua.hh
@@ -1,45 +1,54 @@
+/* == Start of generated table == */
+/*
+ * The following table is generated by running:
+ *
+ *   ./gen-hebrew-pua.py
+ *
+ */
+
 #ifndef HB_OT_SHAPER_HEBREW_PUA_HH
 #define HB_OT_SHAPER_HEBREW_PUA_HH
 
 #include "hb.hh"
 
+#include <stdint.h>
 
-static inline uint16_t
-_hb_hebrew_pua_map (unsigned u)
+static const uint8_t _hb_hebrew_pua_u8[137]=
 {
-  if ((u >= 0x0020u && u <= 0x0040u) ||
-      (u >= 0x005Bu && u <= 0x005Fu) ||
-      (u >= 0x007Bu && u <= 0x007Eu))
-    return 0xF000u + u;
+    1,  8,  0,  0,  0,  0,  0,  0,  3,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  4,  8, 12, 16,  0,
+   20,  0,  0, 24, 28,  0, 32,  0, 36,  0,  0,  0,  0,  0,  0,  0,
+    0,  0,  0,  0, 40, 44, 48, 52,  0, 56,  0,  0,  0,  0,  0,  0,
+   60,  0,  0, 64,  0,  0,  0,  0,  0,  4,  8, 12, 16, 20, 24, 28,
+   32, 36,  0,  0,  0,  0,  0, 40, 44,  0,  0, 48, 52, 56, 60,  0,
+    0, 64, 68, 72, 76,  0, 80,  0,  0,  0, 84,  0,  0, 88, 92, 96,
+  100,104,  0,  0,  0,108,112,116,120,124,128,132,  0,  0,  0,  0,
+  136,140,144,148,  0,  0,  0,152,  0,
+};
+static const uint16_t _hb_hebrew_pua_u16[156]=
+{
+      0,    0,    0,    0,61472,61473,61474,61475,61476,61477,61478,61479,61480,61481,61482,61483,
+  61484,61485,61486,61487,61488,61489,61490,61491,61492,61493,61494,61495,61496,61497,61498,61499,
+  61500,61501,61502,61503,61504,    0,    0,    0,    0,    0,    0,61531,61532,61533,61534,61535,
+      0,    0,    0,61563,61564,61565,61566,    0,    0,    0,    0,61603,    0,    0,    0,61607,
+  61616,    0,61570,61571,    0,    0,    0,61623,    0,61569,    0,    0,61628,61629,61630,    0,
+      0,    0,    0,61610,    0,    0,    0,61626,61632,61633,61634,61635,61636,61637,61638,61639,
+  61640,61641,61642,61643,61644,61645,61646,61647,61648,61649,61650,61651,61664,61665,61666,61667,
+  61668,61669,61670,61671,61672,61673,61674,61675,61676,61677,61678,61679,61680,61681,61682,61683,
+  61684,61685,61686,61687,61688,61689,61690,    0,    0,    0,61693,61694,61568,    0,    0,    0,
+  61572,61573,61574,61575,61576,61577,    0,    0,    0,    0,61604,    0,
+};
 
-  if ((u >= 0x05B0u && u <= 0x05C3u) ||
-      (u >= 0x05D0u && u <= 0x05EAu))
-    return 0xEB10u + u;
-
-  if (u >= 0x2074u && u <= 0x2079u)
-    return 0xD010u + u;
-
-  switch (u)
-  {
-    case 0x00A3u: return 0xF0A3u;
-    case 0x00A7u: return 0xF0A7u;
-    case 0x00B0u: return 0xF0B0u;
-    case 0x00B2u: return 0xF082u;
-    case 0x00B3u: return 0xF083u;
-    case 0x00B7u: return 0xF0B7u;
-    case 0x00B9u: return 0xF081u;
-    case 0x00BCu: return 0xF0BCu;
-    case 0x00BDu: return 0xF0BDu;
-    case 0x00BEu: return 0xF0BEu;
-    case 0x00D7u: return 0xF0AAu;
-    case 0x00F7u: return 0xF0BAu;
-    case 0x200Eu: return 0xF0FDu;
-    case 0x200Fu: return 0xF0FEu;
-    case 0x2070u: return 0xF080u;
-    case 0x20AAu: return 0xF0A4u;
-    default:      return 0;
-  }
+static inline uint8_t _hb_hebrew_pua_b2 (const uint8_t* a, unsigned i)
+{
+  return (a[i>>2]>>((i&3)<<1))&3;
+}
+static inline uint16_t _hb_hebrew_pua_map (unsigned u)
+{
+  /* packtab: [2^2,2^4,2^2,2^2] */
+  return u<8363u ? (uint16_t)(_hb_hebrew_pua_u16[_hb_hebrew_pua_u8[69u+_hb_hebrew_pua_u8[9u+((_hb_hebrew_pua_b2(_hb_hebrew_pua_u8,((((((u)>>2))>>2))>>4)))<<4)+((((((u)>>2))>>2))&15)]+((((u)>>2))&3)]+((u)&3)]) : 0;
 }
 
-
 #endif /* HB_OT_SHAPER_HEBREW_PUA_HH */
+
+/* == End of generated table == */
diff --git a/src/update-unicode-tables.make b/src/update-unicode-tables.make
index e4033dd..fdbbd59 100755
--- a/src/update-unicode-tables.make
+++ b/src/update-unicode-tables.make
@@ -4,6 +4,7 @@
 	hb-ot-shaper-arabic-joining-list.hh \
 	hb-ot-shaper-arabic-pua.hh \
 	hb-ot-shaper-arabic-table.hh \
+	hb-ot-shaper-hebrew-pua.hh \
 	hb-unicode-emoji-table.hh \
 	hb-ot-shaper-indic-table.cc \
 	hb-ot-tag-table.hh \
@@ -26,6 +27,8 @@
 	./$< > $@ || ($(RM) $@; false)
 hb-ot-shaper-arabic-table.hh: gen-arabic-table.py ArabicShaping.txt UnicodeData.txt Blocks.txt
 	./$^ > $@ || ($(RM) $@; false)
+hb-ot-shaper-hebrew-pua.hh: gen-hebrew-pua.py HebrewPUA.txt
+	./$< > $@ || ($(RM) $@; false)
 hb-unicode-emoji-table.hh: gen-emoji-table.py emoji-data.txt emoji-test.txt
 	./$^ > $@ || ($(RM) $@; false)
 hb-ot-shaper-indic-table.cc: gen-indic-table.py IndicSyllabicCategory.txt IndicPositionalCategory.txt Blocks.txt