aboutsummaryrefslogtreecommitdiffstats
path: root/text
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-28 15:20:08 +0100
committereunhhu <[email protected]>2026-09-28 15:20:08 +0100
commitbafb710f0047751e26b87897eaf2a05609708536 (patch)
treea5ccb5e8a3c7cc936b1b9f2c7e46fbeeee36cf92 /text
parent218298bb16ef6c147aa6dafc3ea88f62c6fa6ef6 (diff)
downloadvega-bafb710f0047751e26b87897eaf2a05609708536.tar.gz
vega-bafb710f0047751e26b87897eaf2a05609708536.zip
Shape Syriac, stretch with stch, and fall back for fonts without Arabic forms
The rest of HarfBuzz 10.2's Arabic shaper. Joining types now come from HarfBuzz's own table (Unicode 16, with Syriac's Alaph and Dalath-Rish groups the state machine's last two columns use), the general category deciding where it has none, so Syriac shapes with fin2, fin3 and med2. What stch multiplies is recorded and, after positioning, stretched under the word before it, repeating pieces copied as often as fit. A font with presentation forms but no Arabic features gets HarfBuzz's fallback lookups, which the build tool writes as OpenType and the runtime applies at their pause like any GSUB lookup. The generator now reads HarfBuzz's generated tables through their lookup functions' ranges and offsets: rows whose range starts mid-row have blank cells in front, which the row comments alone miscounted (Arabic Supplement); the Indic table, read again this way, did not change. Noto Sans Syriac, FreeSans's Syriac, Unifont's Arabic and Noto Sans Arabic shape as HarfBuzz does at 3,000 runs a plan; no installed font is refused any more (213 fonts, 1,822 plans).
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()):