aboutsummaryrefslogtreecommitdiffstats
path: root/text
diff options
context:
space:
mode:
Diffstat (limited to 'text')
-rw-r--r--text/shape-build.c222
-rw-r--r--text/shape.c177
-rw-r--r--text/shape.h21
-rw-r--r--text/syllabic-data.c328
-rw-r--r--text/syllabic.h13
-rw-r--r--text/syllabic.py105
6 files changed, 831 insertions, 35 deletions
diff --git a/text/shape-build.c b/text/shape-build.c
index 96bc748..7cb251d 100644
--- a/text/shape-build.c
+++ b/text/shape-build.c
@@ -334,7 +334,9 @@ static int categorize(uint32_t script, hb_tag_t gsub_script) {
if (script == TAG("Hang")) return VEGA_SHAPER_HANGUL;
if (script == TAG("Khmr")) return VEGA_SHAPER_KHMER;
if (script == TAG("Thai") || script == TAG("Laoo")) return VEGA_SHAPER_THAI;
- if (script == TAG("Syrc") || script == TAG("Qaag")) return -1;
+ /* Syriac is Arabic's, unless the font has only DFLT for it. */
+ if (script == TAG("Syrc")) return gsub_script != HB_OT_TAG_DEFAULT_SCRIPT ? VEGA_SHAPER_ARABIC : VEGA_SHAPER_DEFAULT;
+ if (script == TAG("Qaag")) return -1;
if (listed(indic_scripts, sizeof(indic_scripts) / sizeof(*indic_scripts), script))
return dflt ? VEGA_SHAPER_DEFAULT : (gsub_script & 0xFF) == '3' ? VEGA_SHAPER_USE : VEGA_SHAPER_INDIC;
if (listed(use_scripts, sizeof(use_scripts) / sizeof(*use_scripts), script))
@@ -466,7 +468,7 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
if (shaper == VEGA_SHAPER_ARABIC) {
m->is_simple = false;
enable_feature(m, TAG("stch"), F_NONE, 1);
- add_gsub_pause(m, 0); /* record_stch */
+ add_gsub_pause(m, VEGA_PAUSE_RECORD_STCH);
enable_feature(m, TAG("ccmp"), F_MANUAL_ZWJ, 1);
enable_feature(m, TAG("locl"), F_MANUAL_ZWJ, 1);
add_gsub_pause(m, 0);
@@ -476,7 +478,7 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
add_gsub_pause(m, 0);
}
enable_feature(m, TAG("rlig"), F_MANUAL_ZWJ | F_HAS_FALLBACK, 1);
- if (script == HB_SCRIPT_ARABIC) add_gsub_pause(m, 0); /* arabic_fallback_shape */
+ if (script == HB_SCRIPT_ARABIC) add_gsub_pause(m, VEGA_PAUSE_ARABIC_FALLBACK);
enable_feature(m, TAG("calt"), F_MANUAL_ZWJ, 1);
if (!has_feature(m, TAG("rclt"))) add_gsub_pause(m, 0);
enable_feature(m, TAG("liga"), F_MANUAL_ZWJ, 1);
@@ -614,15 +616,15 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
bool zeroes = shaper != VEGA_SHAPER_HANGUL && shaper != VEGA_SHAPER_INDIC && shaper != VEGA_SHAPER_KHMER;
bool script_marks = zeroes && shaper != VEGA_SHAPER_MYANMAR && shaper != VEGA_SHAPER_USE && shaper != VEGA_SHAPER_THAI;
bool adjust = !apply_gpos && (!apply_kern || !kern_table.cross), fallback_marks = adjust && script_marks;
+ bool arabic_fallback = false;
if (shaper == VEGA_SHAPER_ARABIC) {
- if (one_mask(m, "stch")) FAIL("%s: the font stretches (stch)", name);
bool fallback = script == HB_SCRIPT_ARABIC;
for (int i = 0; i < 7; ++i) {
const FeatureMap *f = find_feature(m, TAG(arabic_features[i]));
bool syriac = arabic_features[i][3] == '2' || arabic_features[i][3] == '3';
fallback = fallback && (syriac || (f && f->needs_fallback));
}
- if (fallback) FAIL("%s: the font lacks Arabic's forms (HarfBuzz would fall back)", name);
+ arabic_fallback = fallback;
}
hb_direction_t native = hb_script_get_horizontal_direction(script);
uint32_t frac = one_mask(m, "frac"), numr = one_mask(m, "numr"), dnom = one_mask(m, "dnom");
@@ -641,6 +643,9 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
plan->masks[VEGA_MASK_DNOM] = dnom;
if (shaper == VEGA_SHAPER_ARABIC || shaper == VEGA_SHAPER_USE)
for (int i = 0; i < 7; ++i) plan->masks[VEGA_MASK_ISOL + i] = one_mask(m, arabic_features[i]);
+ if (shaper == VEGA_SHAPER_ARABIC) plan->masks[VEGA_MASK_ARABIC_RLIG] = one_mask(m, "rlig");
+ if (arabic_fallback) plan->shaper_flags |= VEGA_ARABIC_FALLBACK;
+ if (shaper == VEGA_SHAPER_ARABIC && one_mask(m, "stch")) plan->shaper_flags |= VEGA_ARABIC_STCH;
if (shaper == VEGA_SHAPER_USE) plan->masks[VEGA_MASK_USE_RPHF] = one_mask(m, "rphf");
if (shaper == VEGA_SHAPER_HANGUL) {
plan->masks[VEGA_MASK_LJMO] = one_mask(m, "ljmo");
@@ -705,6 +710,194 @@ static void set32(Out *o, size_t at, uint32_t v) {
for (int i = 0; i < 4; ++i) o->bytes[at + i] = (unsigned char)(v >> (8 * i));
}
+/* ---- Arabic fallback shaping (hb-ot-shaper-arabic-fallback.hh): for a font
+ * with presentation forms but no Arabic features, lookups made from its
+ * character map, written as OpenType (the runtime applies them as any
+ * GSUB lookup): single substitutions to the init, medi, fina and isol
+ * forms, and rlig's ligatures of three, of two, and of marks. */
+
+typedef struct { unsigned char *bytes; size_t size, allocated; bool failed; } Bytes;
+static void be16(Bytes *b, uint32_t v) {
+ if (b->failed) return;
+ if (b->size + 2 > b->allocated) {
+ size_t a = b->allocated ? 2 * b->allocated : 256;
+ unsigned char *grown = realloc(b->bytes, a);
+ if (!grown) {
+ b->failed = true;
+ return;
+ }
+ b->bytes = grown;
+ b->allocated = a;
+ }
+ b->bytes[b->size++] = (unsigned char)(v >> 8);
+ b->bytes[b->size++] = (unsigned char)v;
+}
+static void be16_at(Bytes *b, size_t at, uint32_t v) {
+ if (b->failed) return;
+ b->bytes[at] = (unsigned char)(v >> 8);
+ b->bytes[at + 1] = (unsigned char)v;
+}
+
+static bool glyph_of(hb_font_t *font, uint32_t u, uint32_t *glyph) {
+ hb_codepoint_t g;
+ if (!u || !hb_font_get_nominal_glyph(font, u, &g)) return false;
+ *glyph = g;
+ return true;
+}
+
+/* Glyphs sorted stably, their partners carried along; false if two are
+ * the same (the font maps two characters to one glyph). */
+static bool sort_pairs(uint32_t *keys, uint32_t *values, size_t n) {
+ for (size_t i = 1; i < n; ++i)
+ for (size_t j = i; j > 0 && keys[j - 1] > keys[j]; --j) {
+ uint32_t k = keys[j], v = values[j];
+ keys[j] = keys[j - 1], values[j] = values[j - 1];
+ keys[j - 1] = k, values[j - 1] = v;
+ }
+ for (size_t i = 1; i < n; ++i)
+ if (keys[i] == keys[i - 1]) return false;
+ return true;
+}
+
+/* A lookup (type, flags, one subtable) at the end of b; the subtable's
+ * start, for its offsets. */
+static size_t start_lookup(Bytes *b, unsigned type, unsigned flags) {
+ size_t at = b->size;
+ be16(b, type);
+ be16(b, flags);
+ be16(b, 1);
+ be16(b, 8); /* the subtable, right after */
+ return at + 8;
+}
+static void coverage_of(Bytes *b, const uint32_t *glyphs, size_t n) {
+ be16(b, 1);
+ be16(b, (uint32_t)n);
+ for (size_t i = 0; i < n; ++i) be16(b, glyphs[i]);
+}
+
+/* The lookup for fallback feature k into b; false with nothing written
+ * when it would be empty, and *refused when the font cannot have it. */
+static bool fallback_lookup(hb_font_t *font, int k, Bytes *b, bool *refused) {
+ enum { FORMS = VEGA_ARABIC_FORMS_LAST - VEGA_ARABIC_FORMS_FIRST + 1 };
+ if (k < 4) {
+ uint32_t glyphs[FORMS], substitutes[FORMS];
+ size_t n = 0;
+ for (uint32_t u = VEGA_ARABIC_FORMS_FIRST; u <= VEGA_ARABIC_FORMS_LAST; ++u) {
+ uint32_t g, s;
+ if (!glyph_of(font, u, &g) || !glyph_of(font, vega_arabic_forms[u - VEGA_ARABIC_FORMS_FIRST][k], &s) || g == s)
+ continue;
+ glyphs[n] = g;
+ substitutes[n++] = s;
+ }
+ if (!n) return false;
+ if (!sort_pairs(glyphs, substitutes, n)) return !(*refused = true);
+ size_t sub = start_lookup(b, 1, 0x0008);
+ be16(b, 2);
+ be16(b, (uint32_t)(6 + 2 * n)); /* the coverage, after the substitutes */
+ be16(b, (uint32_t)n);
+ for (size_t i = 0; i < n; ++i) be16(b, substitutes[i]);
+ coverage_of(b, glyphs, n);
+ (void)sub;
+ return true;
+ }
+ const VegaArabicLigatures *table = &vega_arabic_ligatures[k - 4];
+ /* its sets: runs of one first character */
+ uint32_t firsts[64], sets[64], set_start[65];
+ size_t n_sets = 0;
+ for (unsigned i = 0; i < table->count; ++i)
+ if (!i || table->ligatures[i].first != table->ligatures[i - 1].first) {
+ if (n_sets == 64) return !(*refused = true);
+ set_start[n_sets++] = i;
+ }
+ set_start[n_sets] = table->count;
+ size_t n = 0;
+ for (size_t i = 0; i < n_sets; ++i)
+ if (glyph_of(font, table->ligatures[set_start[i]].first, &firsts[n])) sets[n++] = (uint32_t)i;
+ if (!sort_pairs(firsts, sets, n)) return !(*refused = true);
+ /* what each set keeps: ligatures whose glyph and components the font has */
+ unsigned total = 0;
+ for (size_t i = 0; i < n; ++i)
+ for (uint32_t l = set_start[sets[i]]; l < set_start[sets[i] + 1]; ++l) {
+ const VegaArabicLigature *lig = &table->ligatures[l];
+ uint32_t g;
+ bool ok = glyph_of(font, lig->ligature, &g);
+ for (unsigned c = 0; ok && c + 1 < table->length; ++c) ok = glyph_of(font, lig->components[c], &g);
+ total += ok;
+ }
+ if (!total) return false;
+ size_t sub = start_lookup(b, 4, k == 6 ? 0 : 0x0008);
+ be16(b, 1);
+ size_t coverage_at = b->size;
+ be16(b, 0);
+ be16(b, (uint32_t)n);
+ size_t offsets_at = b->size;
+ for (size_t i = 0; i < n; ++i) be16(b, 0);
+ for (size_t i = 0; i < n; ++i) {
+ be16_at(b, offsets_at + 2 * i, (uint32_t)(b->size - sub));
+ size_t set_at = b->size, count_at = b->size;
+ be16(b, 0);
+ unsigned count = 0;
+ size_t lig_offsets = b->size;
+ for (uint32_t l = set_start[sets[i]]; l < set_start[sets[i] + 1]; ++l) {
+ const VegaArabicLigature *lig = &table->ligatures[l];
+ uint32_t g;
+ bool ok = glyph_of(font, lig->ligature, &g);
+ for (unsigned c = 0; ok && c + 1 < table->length; ++c) ok = glyph_of(font, lig->components[c], &g);
+ if (ok) be16(b, 0), count++;
+ }
+ be16_at(b, count_at, count);
+ unsigned written = 0;
+ for (uint32_t l = set_start[sets[i]]; l < set_start[sets[i] + 1]; ++l) {
+ const VegaArabicLigature *lig = &table->ligatures[l];
+ uint32_t g, components[2];
+ bool ok = glyph_of(font, lig->ligature, &g);
+ for (unsigned c = 0; ok && c + 1 < table->length; ++c) ok = glyph_of(font, lig->components[c], &components[c]);
+ if (!ok) continue;
+ be16_at(b, lig_offsets + 2 * written++, (uint32_t)(b->size - set_at));
+ be16(b, g);
+ be16(b, table->length);
+ for (unsigned c = 0; c + 1 < table->length; ++c) be16(b, components[c]);
+ }
+ }
+ be16_at(b, coverage_at, (uint32_t)(b->size - sub));
+ coverage_of(b, firsts, n);
+ return true;
+}
+
+/* The fallback lookups as a GSUB table (empty script and feature lists),
+ * and what each lookup is for. */
+static bool fallback_table(hb_font_t *font, Bytes *gsub, uint8_t *features, unsigned *count, char *error,
+ size_t error_size) {
+ Bytes lookups[7] = {{0}};
+ *count = 0;
+ bool refused = false;
+ for (int k = 0; k < 7 && !refused; ++k)
+ if (fallback_lookup(font, k, &lookups[*count], &refused)) features[(*count)++] = (uint8_t)k;
+ if (refused) {
+ snprintf(error, error_size, "Arab: the font maps two characters to one glyph among those fallback shaping uses");
+ for (int k = 0; k < 7; ++k) free(lookups[k].bytes);
+ return false;
+ }
+ be16(gsub, 1);
+ be16(gsub, 0);
+ be16(gsub, 10); /* scripts */
+ be16(gsub, 12); /* features */
+ be16(gsub, 14); /* lookups */
+ be16(gsub, 0);
+ be16(gsub, 0);
+ be16(gsub, *count);
+ size_t list = 14, offsets = gsub->size;
+ for (unsigned i = 0; i < *count; ++i) be16(gsub, 0);
+ for (unsigned i = 0; i < *count; ++i) {
+ be16_at(gsub, offsets + 2 * i, (uint32_t)(gsub->size - list));
+ for (size_t k = 0; k < lookups[i].size; k += 2) be16(gsub, (uint32_t)lookups[i].bytes[k] << 8 | lookups[i].bytes[k + 1]);
+ }
+ bool failed = gsub->failed || gsub->size > 0xFFFF;
+ for (int k = 0; k < 7; ++k) failed |= lookups[k].failed, free(lookups[k].bytes);
+ if (failed) snprintf(error, error_size, "Arab: fallback lookups: out of memory or over 64 KiB");
+ return !failed;
+}
+
bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t script_count, const uint32_t *codes,
size_t code_count, VegaSection *out, hb_set_t *glyphs, char *error, size_t error_size) {
Plan *plans = calloc(2 * script_count + 1, sizeof(*plans));
@@ -794,6 +987,25 @@ bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t scrip
put16(&o, (uint32_t)((known ? e.height : 0) & 0xFFFF));
}
}
+ bool falls_back = false;
+ for (size_t i = 0; i < plan_count; ++i) falls_back |= plans[i].shaper_flags & VEGA_ARABIC_FALLBACK;
+ if (falls_back) {
+ Bytes gsub = {0};
+ uint8_t features[7] = {0};
+ unsigned count;
+ if (!fallback_table(font, &gsub, features, &count, error, error_size)) {
+ free(gsub.bytes);
+ goto fail;
+ }
+ align4(&o);
+ set32(&o, 76, (uint32_t)o.size);
+ unsigned char head[8] = {(unsigned char)count};
+ memcpy(head + 1, features, 7);
+ put(&o, head, 8);
+ put32(&o, (uint32_t)gsub.size);
+ put(&o, gsub.bytes, gsub.size);
+ free(gsub.bytes);
+ }
align4(&o);
set32(&o, 48, (uint32_t)plan_count);
set32(&o, 52, (uint32_t)o.size);
diff --git a/text/shape.c b/text/shape.c
index 20fea05..c9b8c0c 100644
--- a/text/shape.c
+++ b/text/shape.c
@@ -128,16 +128,17 @@ static uint32_t first_coverage(VegaTable t, int table, const VegaSubtable *st) {
return offset16(t, at, at + 2);
}
-/* A table's lookups, their subtables' types resolved through extensions. */
-static bool resolve_lookups(VegaShaping *s, int table, size_t *subtables, size_t *allocated) {
- VegaTable t = table ? s->gpos : s->gsub;
+/* A table's lookups (table 0 GSUB, 1 GPOS), their subtables' types
+ * resolved through extensions. */
+static bool resolve_lookups(VegaShaping *s, VegaTable t, int table, VegaLookup **out, uint32_t *out_count,
+ size_t *subtables, size_t *allocated) {
uint32_t list = t.size ? offset16(t, 0, 8) : 0, count = list ? get16(t, list) : 0;
- s->lookup_count[table] = count;
- if (!(s->lookups[table] = calloc(count ? count : 1, sizeof(VegaLookup)))) return false;
+ *out_count = count;
+ if (!(*out = calloc(count ? count : 1, sizeof(VegaLookup)))) return false;
uint16_t extension = table ? 9 : 7;
for (uint32_t i = 0; i < count; ++i) {
uint32_t at = offset16(t, list, list + 2 + 2 * i), n = at ? get16(t, at + 4) : 0;
- VegaLookup *l = &s->lookups[table][i];
+ VegaLookup *l = &(*out)[i];
l->type = (uint16_t)get16(t, at);
l->flags = (uint16_t)get16(t, at + 2);
l->props = l->flags | (l->flags & 0x10 ? get16(t, at + 6 + 2 * n) << 16 : 0);
@@ -222,8 +223,20 @@ bool vega_shaping_open(VegaShaping *s, const unsigned char *bytes, size_t size,
s->mark_sets = minor >= 2 ? offset16(s->gdef, 0, 12) : 0;
}
size_t subtables = 0, allocated = 0;
- if (!resolve_lookups(s, 0, &subtables, &allocated) || !resolve_lookups(s, 1, &subtables, &allocated))
+ uint32_t fallback = le32(bytes + 76);
+ if (fallback) {
+ if (!within(size, fallback, 12) || bytes[fallback] > 7 || !within(size, fallback + 12, le32(bytes + fallback + 8)))
+ REFUSE("fallback lookups");
+ s->fallback = (VegaTable){bytes + fallback + 12, le32(bytes + fallback + 8)};
+ memcpy(s->fallback_features, bytes + fallback + 1, 7);
+ }
+ if (!resolve_lookups(s, s->gsub, 0, &s->lookups[0], &s->lookup_count[0], &subtables, &allocated) ||
+ !resolve_lookups(s, s->gpos, 1, &s->lookups[1], &s->lookup_count[1], &subtables, &allocated) ||
+ !resolve_lookups(s, s->fallback, 0, &s->fallback_lookups, &s->fallback_count, &subtables, &allocated))
REFUSE("out of memory");
+ if (fallback && s->fallback_count != bytes[fallback]) REFUSE("fallback lookups");
+ for (uint32_t i = 0; i < s->fallback_count; ++i)
+ if (s->fallback_features[i] > 6) REFUSE("fallback lookups");
for (uint32_t i = 0; i < s->plan_count; ++i) {
const unsigned char *p = s->plans + (size_t)i * VEGA_PLAN_BYTES;
uint32_t ranges[2][2] = {{le32(p + 80), le32(p + 84)}, {le32(p + 88), le32(p + 92)}};
@@ -236,7 +249,7 @@ bool vega_shaping_open(VegaShaping *s, const unsigned char *bytes, size_t size,
for (uint32_t k = 0; k < ranges[table][1]; ++k) {
const unsigned char *step = s->steps + (size_t)(ranges[table][0] + k) * VEGA_STEP_BYTES;
bool pause = le16(step) == VEGA_STEP_PAUSE;
- if (pause ? table || !step[2] || step[2] > VEGA_PAUSE_RECORD_PREF : le16(step) >= s->lookup_count[table])
+ if (pause ? table || !step[2] || step[2] > VEGA_PAUSE_RECORD_STCH : le16(step) >= s->lookup_count[table])
REFUSE("plan %u: a lookup the font lacks", i);
}
}
@@ -250,6 +263,8 @@ void vega_shaping_close(VegaShaping *s) {
for (int i = 0; i < 256; ++i) free(s->bmp[i]), s->bmp[i] = NULL;
free(s->lookups[0]);
free(s->lookups[1]);
+ free(s->fallback_lookups);
+ s->fallback_lookups = NULL;
free(s->subtables);
s->lookups[0] = s->lookups[1] = NULL;
s->subtables = NULL;
@@ -326,7 +341,7 @@ struct VegaShaper {
};
enum { HAS_NON_ASCII = 1, HAS_DEFAULT_IGNORABLES = 2, HAS_SPACE_FALLBACK = 4, HAS_GPOS_ATTACHMENT = 8, HAS_CGJ = 16,
- HAS_VARIATION_SELECTOR_FALLBACK = 32, HAS_BROKEN_SYLLABLE = 64 };
+ HAS_VARIATION_SELECTOR_FALLBACK = 32, HAS_BROKEN_SYLLABLE = 64, HAS_STCH = 128 };
VegaShaper *vega_shaper_new(void) { return calloc(1, sizeof(VegaShaper)); }
@@ -1137,17 +1152,16 @@ static void setup_masks_fraction(VegaShaper *b, const Plan *plan) {
}
}
-enum { ISOL, FINA, FIN2, FIN3, MEDI, MED2, INIT, NONE };
+enum { ISOL, FINA, FIN2, FIN3, MEDI, MED2, INIT, NONE, STCH_FIXED, STCH_REPEATING };
enum { JT_U, JT_L, JT_R, JT_D, JG_ALAPH, JG_DALATH_RISH, JT_T = 7 };
-static unsigned joining_type(uint32_t u) {
- switch (vega_joining(u)) {
- case VEGA_JOINING_L: return JT_L;
- case VEGA_JOINING_R: return JT_R;
- case VEGA_JOINING_D: case VEGA_JOINING_C: return JT_D;
- case VEGA_JOINING_T: return JT_T;
- default: return JT_U;
- }
+/* HarfBuzz's joining type (hb-ot-shaper-arabic-table.hh); where its table
+ * has none, T for a non-spacing or enclosing mark or a format character. */
+static unsigned joining_type(const Info *info) {
+ unsigned type = vega_arabic_joining(info->codepoint);
+ if (type != 8) return type;
+ unsigned c = gen_cat(info);
+ return c == VEGA_MN || c == VEGA_ME || c == VEGA_CF ? JT_T : JT_U;
}
static void setup_masks_arabic(VegaShaper *b, const Plan *plan) {
@@ -1162,7 +1176,7 @@ static void setup_masks_arabic(VegaShaper *b, const Plan *plan) {
size_t prev = (size_t)-1;
unsigned state = 0;
for (size_t i = 0; i < b->len; ++i) {
- unsigned type = joining_type(b->info[i].codepoint);
+ unsigned type = joining_type(&b->info[i]);
if (type == JT_T) {
b->info[i].action = NONE;
continue;
@@ -3626,10 +3640,132 @@ static void preprocess_thai(const VegaShaping *s, const Plan *plan, VegaShaper *
if (plan->shaper_flags & VEGA_THAI_PUA) thai_pua_shaping(s, b);
}
+/* Arabic fallback shaping (hb-ot-shaper-arabic-fallback.hh): the lookups
+ * the build tool made from the font's presentation forms, each under its
+ * feature's mask, applied as HarfBuzz applies a GSUB lookup outside a plan
+ * (both joiners skipped, no syllables). */
+static bool arabic_fallback(const VegaShaping *s, const Plan *plan, VegaShaper *b) {
+ if (!(plan->shaper_flags & VEGA_ARABIC_FALLBACK)) return false;
+ static const int forms[4] = {VEGA_MASK_INIT, VEGA_MASK_MEDI, VEGA_MASK_FINA, VEGA_MASK_ISOL};
+ for (uint32_t i = 0; i < s->fallback_count; ++i) {
+ unsigned feature = s->fallback_features[i];
+ uint32_t mask = plan->masks[feature < 4 ? forms[feature] : VEGA_MASK_ARABIC_RLIG];
+ if (!mask) continue;
+ Apply c = {.s = s, .b = b, .table = 0, .t = s->fallback, .lookup_mask = mask, .auto_zwnj = true,
+ .auto_zwj = true, .has_glyph_classes = s->glyph_classes != 0, .nesting_left = MAX_NESTING_LEVEL,
+ .random_state = 1, .last_base = -1};
+ apply_string(&c, &s->fallback_lookups[i]);
+ }
+ return true;
+}
+
+/* Stretching (hb-ot-shaper-arabic.cc's apply_stch), after positioning: each
+ * run of stretch pieces fills the width of the word before it, its
+ * repeating pieces copied as often as fits (overlapping a little to fit
+ * closer), fixed pieces once. Measured first, then cut. */
+static bool arabic_word_category(unsigned c) {
+ switch (c) {
+ case VEGA_CN: case VEGA_CO: case VEGA_LM: case VEGA_LO: case VEGA_MC: case VEGA_ME: case VEGA_MN: case VEGA_ND:
+ case VEGA_NL: case VEGA_NO: case VEGA_SC: case VEGA_SK: case VEGA_SM: case VEGA_SO:
+ return true;
+ default:
+ return false;
+ }
+}
+static bool stretching(const Info *i) { return i->action == STCH_FIXED || i->action == STCH_REPEATING; }
+
+static void apply_stch(const VegaShaping *s, VegaShaper *b) {
+ if (!(b->scratch & HAS_STCH)) return;
+ bool rtl = b->direction == VEGA_SHAPE_RTL;
+ if (!rtl) reverse_range(b, 0, b->len, true);
+ size_t extra = 0;
+ for (int cut = 0; cut <= 1; ++cut) {
+ size_t count = b->len, new_len = count + extra, j = new_len;
+ Info *info = b->info;
+ Position *pos = b->pos;
+ for (size_t i = count; i; i--) {
+ if (!stretching(&info[i - 1])) {
+ if (cut) {
+ --j;
+ info[j] = info[i - 1];
+ pos[j] = pos[i - 1];
+ }
+ continue;
+ }
+ int32_t w_total = 0, w_fixed = 0, w_repeating = 0;
+ int n_repeating = 0;
+ size_t end = i;
+ while (i && stretching(&info[i - 1])) {
+ i--;
+ int32_t width = advance_of(s, info[i].codepoint);
+ if (info[i].action == STCH_FIXED) w_fixed += width;
+ else w_repeating += width, n_repeating++;
+ }
+ size_t start = i, context = i;
+ while (context && !stretching(&info[context - 1]) &&
+ (is_default_ignorable(&info[context - 1]) || arabic_word_category(gen_cat(&info[context - 1])))) {
+ context--;
+ w_total += pos[context].x_advance;
+ }
+ i++;
+ int n_copies = 0;
+ int32_t w_remaining = w_total - w_fixed;
+ if (w_remaining > w_repeating && w_repeating > 0) n_copies = w_remaining / w_repeating - 1;
+ int32_t overlap = 0, shortfall = w_remaining - w_repeating * (n_copies + 1);
+ if (shortfall > 0 && n_repeating > 0) {
+ ++n_copies;
+ int32_t excess = (n_copies + 1) * w_repeating - w_remaining;
+ if (excess > 0) {
+ overlap = excess / (n_copies * n_repeating);
+ w_remaining = 0;
+ }
+ }
+ if (!cut) {
+ extra += (size_t)n_copies * (size_t)n_repeating;
+ continue;
+ }
+ int32_t x_offset = w_remaining / 2;
+ for (size_t k = end; k > start; k--) {
+ int32_t width = advance_of(s, info[k - 1].codepoint);
+ unsigned repeat = 1 + (info[k - 1].action == STCH_REPEATING ? (unsigned)n_copies : 0);
+ pos[k - 1].x_advance = 0;
+ for (unsigned n = 0; n < repeat; n++) {
+ if (rtl) {
+ x_offset -= width;
+ if (n > 0) x_offset += overlap;
+ }
+ pos[k - 1].x_offset = x_offset;
+ --j;
+ info[j] = info[k - 1];
+ pos[j] = pos[k - 1];
+ if (!rtl) {
+ x_offset += width;
+ if (n > 0) x_offset -= overlap;
+ }
+ }
+ }
+ }
+ if (!cut) {
+ if (!ensure(b, count + extra)) break;
+ } else
+ b->len = new_len;
+ }
+ if (!rtl) reverse_range(b, 0, b->len, true);
+}
+
/* The shaper's part of the work at a pause in GSUB; whether it changed the
* buffer. */
static bool shaper_pause(const VegaShaping *s, const Plan *plan, VegaShaper *b, unsigned pause) {
switch (plan->shaper) {
+ case VEGA_SHAPER_ARABIC:
+ /* stch just applied: what it multiplied is stretched after positioning */
+ if (pause == VEGA_PAUSE_RECORD_STCH && (plan->shaper_flags & VEGA_ARABIC_STCH))
+ for (size_t i = 0; i < b->len; ++i)
+ if (b->info[i].gprops & MULTIPLIED) {
+ b->info[i].action = lig_comp(&b->info[i]) % 2 ? STCH_REPEATING : STCH_FIXED;
+ b->scratch |= HAS_STCH;
+ }
+ return pause == VEGA_PAUSE_ARABIC_FALLBACK && arabic_fallback(s, plan, b);
case VEGA_SHAPER_INDIC: {
Indic d = indic_of(s, plan);
switch (pause) {
@@ -3843,6 +3979,9 @@ long vega_shape(VegaShaper *b, const VegaShaping *s, const uint32_t *codes, size
}
}
+ if (plan.shaper == VEGA_SHAPER_ARABIC) apply_stch(s, b);
+ if (!b->successful) return -1;
+
if (b->glyphs_allocated < b->len) {
VegaShaped *glyphs = realloc(b->glyphs, b->len * sizeof(*glyphs));
if (!glyphs) return -1;
diff --git a/text/shape.h b/text/shape.h
index 6e56819..9381754 100644
--- a/text/shape.h
+++ b/text/shape.h
@@ -31,7 +31,12 @@
* y_bearing, width, height; 0 when
* no plan positions marks without
* GPOS; x_bearing INT16_MIN: none),
- * u32 0
+ * u32 fallback_offset (Arabic fallback shaping, 0 when no
+ * plan falls back: u8 lookups, u8
+ * feature[7] each is for (init,
+ * medi, fina, isol, rlig x3), u32
+ * size, then a GSUB table of just
+ * those lookups)
* Offsets are from the section's start. A plan (VEGA_PLAN_BYTES):
* u32 script (ISO 15924 as a tag: 'Latn'), u8 direction, u8 native
* direction (of its script; 0 when it has none), u8 shaper, u8 flags,
@@ -67,18 +72,22 @@ enum { VEGA_MASK_RPHF = VEGA_MASK_SHAPER, VEGA_MASK_PREF, VEGA_MASK_BLWF, VEGA_M
enum { VEGA_MASK_KHMER_PREF = VEGA_MASK_SHAPER, VEGA_MASK_KHMER_BLWF, VEGA_MASK_KHMER_ABVF, VEGA_MASK_KHMER_PSTF,
VEGA_MASK_KHMER_CFAR }; /* Khmer */
enum { VEGA_MASK_USE_RPHF = VEGA_MASK_INIT + 1 }; /* USE: that, and Arabic's for its joining forms */
+enum { VEGA_MASK_ARABIC_RLIG = VEGA_MASK_INIT + 1 }; /* Arabic, for fallback shaping */
/* The stages a plan names: Indic's, whose features decide a consonant's
* position by whether they would substitute it. */
enum { VEGA_STAGE_RPHF, VEGA_STAGE_PREF, VEGA_STAGE_BLWF, VEGA_STAGE_PSTF, VEGA_STAGE_VATU };
/* Shaper flags: Indic's font follows the old specification (deva, not
* dev2); Thai's font has no Thai script of its own, so its marks take the
- * font's private-use glyphs where it has them. */
-enum { VEGA_INDIC_OLD_SPEC = 1, VEGA_THAI_PUA = 2 };
+ * font's private-use glyphs where it has them; Arabic's font lacks Arabic's
+ * forms, so lookups made from its presentation forms stand in; Arabic's
+ * font stretches (stch) what the stretching feature multiplied. */
+enum { VEGA_INDIC_OLD_SPEC = 1, VEGA_THAI_PUA = 2, VEGA_ARABIC_FALLBACK = 4, VEGA_ARABIC_STCH = 8 };
/* Step flags; pauses, which the shaper tells apart. */
enum { VEGA_STEP_AUTO_ZWNJ = 1, VEGA_STEP_AUTO_ZWJ = 2, VEGA_STEP_RANDOM = 4, VEGA_STEP_PER_SYLLABLE = 8 };
enum { VEGA_STEP_PAUSE = 0xFFFF };
enum { VEGA_PAUSE_SYLLABLES = 1, VEGA_PAUSE_INITIAL_REORDERING, VEGA_PAUSE_FINAL_REORDERING, VEGA_PAUSE_CLEAR_SYLLABLES,
- VEGA_PAUSE_CLEAR_SUBSTITUTED, VEGA_PAUSE_RECORD_RPHF, VEGA_PAUSE_RECORD_PREF };
+ VEGA_PAUSE_CLEAR_SUBSTITUTED, VEGA_PAUSE_RECORD_RPHF, VEGA_PAUSE_RECORD_PREF, VEGA_PAUSE_ARABIC_FALLBACK,
+ VEGA_PAUSE_RECORD_STCH };
typedef struct { const unsigned char *p; uint32_t size; } VegaTable;
/* Which glyphs may be in a set, as three 64-bit patterns of glyph bits
@@ -103,6 +112,10 @@ typedef struct {
uint32_t cmap_count, plan_count;
const unsigned char *cmap, *advances, *plans, *steps, *extents;
VegaTable gdef, gsub, gpos, kern;
+ VegaTable fallback; /* Arabic fallback shaping's lookups, a GSUB table */
+ uint32_t fallback_count;
+ uint8_t fallback_features[7]; /* what each is for: init, medi, fina, isol, rlig x3 (0..6) */
+ VegaLookup *fallback_lookups;
uint32_t glyph_classes, attach_classes, mark_sets; /* in GDEF; 0 when absent */
VegaLookup *lookups[2]; /* GSUB, GPOS */
uint32_t lookup_count[2];
diff --git a/text/syllabic-data.c b/text/syllabic-data.c
index cfff1b5..cd7e59b 100644
--- a/text/syllabic-data.c
+++ b/text/syllabic-data.c
@@ -452,6 +452,334 @@ static const uint16_t use_u16[856] = {
static unsigned use_b4(const uint8_t *a, unsigned i) { return (a[i >> 1] >> ((i & 1u) << 2)) & 15u; }
uint8_t vega_use_category(uint32_t u) { return (uint8_t)(u<921600u?use_u8[2953+(((use_u8[625+(((use_u16[((use_u8[113+(((use_b4(use_u8,u>>1>>3>>3>>5))<<5)+((u>>1>>3>>3)&31u))])<<3)+((u>>1>>3)&7u)])<<3)+((u>>1)&7u))])<<1)+((u)&1u))]:0); }
+/* Arabic joining types (hb-ot-shaper-arabic-table.hh, Unicode 16): U 0, L 1, R 2, D and C 3,
+ * Alaph 4, Dalath-Rish 5, T 7; 8 where the general category decides. */
+static const uint8_t joining_cells[1434] = {
+ 0, 0, 0, 0, 0, 0, 8, 8, 0, 8, 8, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 3, 0, 2, 2, 2, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 3, 3, 8, 2, 2, 2, 0, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 3, 3, 2, 2, 8, 2, 8, 8, 8, 8, 8, 8, 8, 0, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 2, 2, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 3, 3, 3, 8, 8, 3,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 4, 8, 3, 3, 3, 5, 5, 2, 2, 2, 3, 3, 3, 3, 2, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 5, 3, 2, 3, 3, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 3, 3, 3, 3, 2, 3, 2, 2, 3, 3, 3, 2, 2, 3, 3, 3, 3, 3, 3,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 3, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 2, 3, 3, 3, 3, 3, 2, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 2, 2, 2, 8, 8, 8, 8, 8, 8, 8,
+ 3, 0, 3, 3, 3, 3, 0, 2, 3, 2, 2, 8, 8, 8, 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 3, 3, 3, 3, 0, 0, 3, 3, 3, 3, 3, 2, 8, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 0, 2, 3, 3, 2, 2, 3, 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 0, 0, 3, 8, 8, 3, 8, 8, 8, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0,
+ 0, 0, 7, 7, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 8, 3, 0, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 0, 3, 3, 3, 3, 3, 2,
+ 0, 2, 0, 2, 2, 0, 0, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 1, 3, 3, 3, 3, 3, 2, 3, 3, 3, 2, 0, 0, 2, 8,
+ 8, 8, 8, 8, 8, 3, 3, 3, 3, 2, 3, 2, 3, 2, 2, 2, 3, 3, 3, 2, 3, 3, 2, 3, 2, 2, 3, 2, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 2, 2, 2, 2, 3, 3, 0, 1, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 3, 2, 3,
+ 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 3, 3, 3, 2, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 3, 3, 3, 3, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 3, 0, 3, 3, 2, 2, 2, 0, 3, 2, 2, 3, 3, 2, 3, 3, 0, 3, 2, 2, 3, 0, 0, 0, 0, 2, 3, 1, 0, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 8, 8, 8, 8, 8, 8, 7,
+};
+static const struct { uint32_t first, last, at; } joining_runs[11] = {
+ {0x0600, 0x08E2, 0},
+ {0x1806, 0x18AA, 739},
+ {0x200C, 0x2069, 904},
+ {0xA840, 0xA873, 998},
+ {0x10AC0, 0x10AEF, 1050},
+ {0x10B80, 0x10BAF, 1098},
+ {0x10D00, 0x10D23, 1146},
+ {0x10EC2, 0x10EC4, 1182},
+ {0x10F30, 0x10FCB, 1185},
+ {0x110BD, 0x110CD, 1341},
+ {0x1E900, 0x1E94B, 1358},
+};
+uint8_t vega_arabic_joining(uint32_t u) {
+ for (unsigned i = 0; i < 11 && u >= joining_runs[i].first; ++i)
+ if (u <= joining_runs[i].last) return joining_cells[joining_runs[i].at + u - joining_runs[i].first];
+ return 8;
+}
+
+/* Presentation forms of U+0621..U+06D3: init, medi, fina, isol. */
+const uint16_t vega_arabic_forms[179][4] = {
+ {0x0000, 0x0000, 0x0000, 0xFE80},
+ {0x0000, 0x0000, 0xFE82, 0xFE81},
+ {0x0000, 0x0000, 0xFE84, 0xFE83},
+ {0x0000, 0x0000, 0xFE86, 0xFE85},
+ {0x0000, 0x0000, 0xFE88, 0xFE87},
+ {0xFE8B, 0xFE8C, 0xFE8A, 0xFE89},
+ {0x0000, 0x0000, 0xFE8E, 0xFE8D},
+ {0xFE91, 0xFE92, 0xFE90, 0xFE8F},
+ {0x0000, 0x0000, 0xFE94, 0xFE93},
+ {0xFE97, 0xFE98, 0xFE96, 0xFE95},
+ {0xFE9B, 0xFE9C, 0xFE9A, 0xFE99},
+ {0xFE9F, 0xFEA0, 0xFE9E, 0xFE9D},
+ {0xFEA3, 0xFEA4, 0xFEA2, 0xFEA1},
+ {0xFEA7, 0xFEA8, 0xFEA6, 0xFEA5},
+ {0x0000, 0x0000, 0xFEAA, 0xFEA9},
+ {0x0000, 0x0000, 0xFEAC, 0xFEAB},
+ {0x0000, 0x0000, 0xFEAE, 0xFEAD},
+ {0x0000, 0x0000, 0xFEB0, 0xFEAF},
+ {0xFEB3, 0xFEB4, 0xFEB2, 0xFEB1},
+ {0xFEB7, 0xFEB8, 0xFEB6, 0xFEB5},
+ {0xFEBB, 0xFEBC, 0xFEBA, 0xFEB9},
+ {0xFEBF, 0xFEC0, 0xFEBE, 0xFEBD},
+ {0xFEC3, 0xFEC4, 0xFEC2, 0xFEC1},
+ {0xFEC7, 0xFEC8, 0xFEC6, 0xFEC5},
+ {0xFECB, 0xFECC, 0xFECA, 0xFEC9},
+ {0xFECF, 0xFED0, 0xFECE, 0xFECD},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFED3, 0xFED4, 0xFED2, 0xFED1},
+ {0xFED7, 0xFED8, 0xFED6, 0xFED5},
+ {0xFEDB, 0xFEDC, 0xFEDA, 0xFED9},
+ {0xFEDF, 0xFEE0, 0xFEDE, 0xFEDD},
+ {0xFEE3, 0xFEE4, 0xFEE2, 0xFEE1},
+ {0xFEE7, 0xFEE8, 0xFEE6, 0xFEE5},
+ {0xFEEB, 0xFEEC, 0xFEEA, 0xFEE9},
+ {0x0000, 0x0000, 0xFEEE, 0xFEED},
+ {0xFBE8, 0xFBE9, 0xFEF0, 0xFEEF},
+ {0xFEF3, 0xFEF4, 0xFEF2, 0xFEF1},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFB51, 0xFB50},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0xFBDD},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB68, 0xFB69, 0xFB67, 0xFB66},
+ {0xFB60, 0xFB61, 0xFB5F, 0xFB5E},
+ {0xFB54, 0xFB55, 0xFB53, 0xFB52},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB58, 0xFB59, 0xFB57, 0xFB56},
+ {0xFB64, 0xFB65, 0xFB63, 0xFB62},
+ {0xFB5C, 0xFB5D, 0xFB5B, 0xFB5A},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB78, 0xFB79, 0xFB77, 0xFB76},
+ {0xFB74, 0xFB75, 0xFB73, 0xFB72},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB7C, 0xFB7D, 0xFB7B, 0xFB7A},
+ {0xFB80, 0xFB81, 0xFB7F, 0xFB7E},
+ {0x0000, 0x0000, 0xFB89, 0xFB88},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFB85, 0xFB84},
+ {0x0000, 0x0000, 0xFB83, 0xFB82},
+ {0x0000, 0x0000, 0xFB87, 0xFB86},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFB8D, 0xFB8C},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFB8B, 0xFB8A},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB6C, 0xFB6D, 0xFB6B, 0xFB6A},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB70, 0xFB71, 0xFB6F, 0xFB6E},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB90, 0xFB91, 0xFB8F, 0xFB8E},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFBD5, 0xFBD6, 0xFBD4, 0xFBD3},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB94, 0xFB95, 0xFB93, 0xFB92},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB9C, 0xFB9D, 0xFB9B, 0xFB9A},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFB98, 0xFB99, 0xFB97, 0xFB96},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFB9F, 0xFB9E},
+ {0xFBA2, 0xFBA3, 0xFBA1, 0xFBA0},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFBAC, 0xFBAD, 0xFBAB, 0xFBAA},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFBA5, 0xFBA4},
+ {0xFBA8, 0xFBA9, 0xFBA7, 0xFBA6},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFBE1, 0xFBE0},
+ {0x0000, 0x0000, 0xFBDA, 0xFBD9},
+ {0x0000, 0x0000, 0xFBD8, 0xFBD7},
+ {0x0000, 0x0000, 0xFBDC, 0xFBDB},
+ {0x0000, 0x0000, 0xFBE3, 0xFBE2},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFBDF, 0xFBDE},
+ {0xFBFE, 0xFBFF, 0xFBFD, 0xFBFC},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0xFBE6, 0xFBE7, 0xFBE5, 0xFBE4},
+ {0x0000, 0x0000, 0x0000, 0x0000},
+ {0x0000, 0x0000, 0xFBAF, 0xFBAE},
+ {0x0000, 0x0000, 0xFBB1, 0xFBB0},
+};
+/* Ligatures fallback shaping forms: three components, two, and marks. */
+static const VegaArabicLigature ligatures_three[3] = {
+ {0xFEDF, 0xFD88, {0xFEE4, 0xFEA4}},
+ {0xFEDF, 0xF201, {0xFEE0, 0xFEEA}},
+ {0xFEDF, 0xF211, {0xFEE4, 0xFEA0}},
+};
+static const VegaArabicLigature ligatures_two[57] = {
+ {0xFE91, 0xFC08, {0xFEE2, 0x0000}},
+ {0xFE91, 0xFC9F, {0xFEE4, 0x0000}},
+ {0xFE91, 0xFC9C, {0xFEA0, 0x0000}},
+ {0xFE91, 0xFC9D, {0xFEA4, 0x0000}},
+ {0xFE91, 0xFC9E, {0xFEA8, 0x0000}},
+ {0xFE92, 0xFC6A, {0xFEAE, 0x0000}},
+ {0xFE92, 0xFC6D, {0xFEE6, 0x0000}},
+ {0xFE92, 0xFC6F, {0xFEF2, 0x0000}},
+ {0xFE97, 0xFC0E, {0xFEE2, 0x0000}},
+ {0xFE97, 0xFCA4, {0xFEE4, 0x0000}},
+ {0xFE97, 0xFCA1, {0xFEA0, 0x0000}},
+ {0xFE97, 0xFCA2, {0xFEA4, 0x0000}},
+ {0xFE97, 0xFCA3, {0xFEA8, 0x0000}},
+ {0xFE98, 0xFC70, {0xFEAE, 0x0000}},
+ {0xFE98, 0xFC73, {0xFEE6, 0x0000}},
+ {0xFE98, 0xFC75, {0xFEF2, 0x0000}},
+ {0xFE9B, 0xFC12, {0xFEE2, 0x0000}},
+ {0xFE9F, 0xFCA8, {0xFEE4, 0x0000}},
+ {0xFEA3, 0xFCAA, {0xFEE4, 0x0000}},
+ {0xFEA7, 0xFCAC, {0xFEE4, 0x0000}},
+ {0xFEB3, 0xFCB0, {0xFEE4, 0x0000}},
+ {0xFEB7, 0xFD30, {0xFEE4, 0x0000}},
+ {0xFED3, 0xFC32, {0xFEF2, 0x0000}},
+ {0xFEDF, 0xFC3F, {0xFE9E, 0x0000}},
+ {0xFEDF, 0xFCC9, {0xFEA0, 0x0000}},
+ {0xFEDF, 0xFC40, {0xFEA2, 0x0000}},
+ {0xFEDF, 0xFCCA, {0xFEA4, 0x0000}},
+ {0xFEDF, 0xFC41, {0xFEA6, 0x0000}},
+ {0xFEDF, 0xFCCB, {0xFEA8, 0x0000}},
+ {0xFEDF, 0xFC42, {0xFEE2, 0x0000}},
+ {0xFEDF, 0xFCCC, {0xFEE4, 0x0000}},
+ {0xFEDF, 0xFC44, {0xFEF2, 0x0000}},
+ {0xFEDF, 0xFCCD, {0xFEEC, 0x0000}},
+ {0xFEDF, 0xFEF5, {0xFE82, 0x0000}},
+ {0xFEDF, 0xFEF7, {0xFE84, 0x0000}},
+ {0xFEDF, 0xFEF9, {0xFE88, 0x0000}},
+ {0xFEDF, 0xFEFB, {0xFE8E, 0x0000}},
+ {0xFEE0, 0xFC86, {0xFEF0, 0x0000}},
+ {0xFEE0, 0xFEF6, {0xFE82, 0x0000}},
+ {0xFEE0, 0xFEF8, {0xFE84, 0x0000}},
+ {0xFEE0, 0xFEFA, {0xFE88, 0x0000}},
+ {0xFEE0, 0xFEFC, {0xFE8E, 0x0000}},
+ {0xFEE3, 0xFCCE, {0xFEA0, 0x0000}},
+ {0xFEE3, 0xFCCF, {0xFEA4, 0x0000}},
+ {0xFEE3, 0xFCD0, {0xFEA8, 0x0000}},
+ {0xFEE3, 0xFCD1, {0xFEE4, 0x0000}},
+ {0xFEE7, 0xFC4E, {0xFEE2, 0x0000}},
+ {0xFEE7, 0xFCD5, {0xFEE4, 0x0000}},
+ {0xFEE7, 0xFCD2, {0xFEA0, 0x0000}},
+ {0xFEE7, 0xFCD3, {0xFEA4, 0x0000}},
+ {0xFEE8, 0xFC8F, {0xFEF2, 0x0000}},
+ {0xFEF3, 0xFCDA, {0xFEA0, 0x0000}},
+ {0xFEF3, 0xFCDB, {0xFEA4, 0x0000}},
+ {0xFEF3, 0xFCDC, {0xFEA8, 0x0000}},
+ {0xFEF3, 0xFCDD, {0xFEE4, 0x0000}},
+ {0xFEF4, 0xFC91, {0xFEAE, 0x0000}},
+ {0xFEF4, 0xFC94, {0xFEE6, 0x0000}},
+};
+static const VegaArabicLigature ligatures_marks[5] = {
+ {0x0651, 0xFC5E, {0x064C, 0x0000}},
+ {0x0651, 0xFC60, {0x064E, 0x0000}},
+ {0x0651, 0xFC61, {0x064F, 0x0000}},
+ {0x0651, 0xFC62, {0x0650, 0x0000}},
+ {0x0651, 0xF2EE, {0x064B, 0x0000}},
+};
+const VegaArabicLigatures vega_arabic_ligatures[3] = {
+ {ligatures_three, 3, 3},
+ {ligatures_two, 57, 2},
+ {ligatures_marks, 5, 2},
+};
+
/* The categories syllabic.h names are the machines' own. */
_Static_assert(VEGA_OT_A == 9, "indic A");
_Static_assert(VEGA_OT_C == 1, "indic C");
diff --git a/text/syllabic.h b/text/syllabic.h
index d03116b..767fada 100644
--- a/text/syllabic.h
+++ b/text/syllabic.h
@@ -51,4 +51,17 @@ typedef struct {
unsigned broken; /* the broken cluster's type, plus one */
} VegaMachine;
extern const VegaMachine vega_indic_machine, vega_khmer_machine, vega_myanmar_machine, vega_use_machine;
+
+/* Arabic's joining type (hb-ot-shaper-arabic-table.hh): U 0, L 1, R 2, D and
+ * C 3, Syriac's Alaph 4 and Dalath-Rish 5, T 7, and 8 where the general
+ * category decides (T for Mn, Me and Cf, else U). */
+uint8_t vega_arabic_joining(uint32_t u);
+/* For fallback shaping: the presentation forms (init, medi, fina, isol) of
+ * U+0621..06D3, and the ligatures of presentation forms, in HarfBuzz's
+ * order: those of three components, of two, of marks. */
+enum { VEGA_ARABIC_FORMS_FIRST = 0x0621, VEGA_ARABIC_FORMS_LAST = 0x06D3 };
+extern const uint16_t vega_arabic_forms[VEGA_ARABIC_FORMS_LAST - VEGA_ARABIC_FORMS_FIRST + 1][4];
+typedef struct { uint16_t first, ligature, components[2]; } VegaArabicLigature;
+typedef struct { const VegaArabicLigature *ligatures; unsigned count, length; } VegaArabicLigatures;
+extern const VegaArabicLigatures vega_arabic_ligatures[3];
#endif
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()):