aboutsummaryrefslogtreecommitdiffstats
path: root/text/shape-build.c
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-28 14:49:05 +0100
committereunhhu <[email protected]>2026-09-28 14:49:05 +0100
commit60a3c0232bbca8d5d8c1107ab8197d272212ef42 (patch)
treeba7b4bad98b79ad1abd40cdb038c493d50df0c19 /text/shape-build.c
parentc23b79f1cb599ee7766002ad23756643e72113e5 (diff)
downloadvega-60a3c0232bbca8d5d8c1107ab8197d272212ef42.tar.gz
vega-60a3c0232bbca8d5d8c1107ab8197d272212ef42.zip
Shape the nine Indic scripts as HarfBuzz does
The Indic shaper, from HarfBuzz 10.2's source: syllables found by the Indic machine; each syllable's base consonant, its pre-base matras and reph moved around it and masked for the basic features, applied one at a time within the syllable; then matras, reph and pre-base-reordering consonants placed again by what the features made. Vowel constraints, Indic decompositions and compositions, dotted circles for broken syllables, and per-syllable matching come with it. Plans carry GSUB pauses (steps where the shaper does its part) and the stages of the features the shaper asks whether they would substitute; the runtime answers with each subtable's would_apply, as HarfBuzz does. Masks are now the common ones and each shaper's own. text/syllabic.py reads HarfBuzz's Ragel grammars (Indic, Khmer, Myanmar) and compiles each to a minimal DFA with Ragel's scanner semantics; beside the machines Ragel generated from the same grammars, 100,000 random runs each find no syllable that differs. It also reads HarfBuzz's Indic table (Unicode 16) cell for cell. Every installed font shapes as HarfBuzz does (213 fonts, 1,688 plans); the Indic ones at 20,000 runs a plan (FreeSerif's eight scripts, FreeSans, Unifont's nine without GSUB), and nine new-spec Noto Sans fonts at 5,000.
Diffstat (limited to 'text/shape-build.c')
-rw-r--r--text/shape-build.c229
1 files changed, 189 insertions, 40 deletions
diff --git a/text/shape-build.c b/text/shape-build.c
index aa653d8..559517e 100644
--- a/text/shape-build.c
+++ b/text/shape-build.c
@@ -3,6 +3,7 @@
* masks and stages), what the section carries, and a check of it against
* HarfBuzz on random runs. */
#include "shape-build.h"
+#include "syllabic.h"
#include "text.h"
#include <hb-ot.h>
#include <stdlib.h>
@@ -25,7 +26,8 @@ typedef struct {
uint32_t mask, one_mask;
bool auto_zwnj, auto_zwj, random, per_syllable, needs_fallback;
} FeatureMap;
-typedef struct { unsigned index; uint32_t mask; bool auto_zwnj, auto_zwj, random, per_syllable; } LookupMap;
+/* A lookup to run, or (index VEGA_STEP_PAUSE) the pause that ends a stage. */
+typedef struct { unsigned index; uint32_t mask; bool auto_zwnj, auto_zwj, random, per_syllable; unsigned pause; } LookupMap;
typedef struct {
hb_face_t *face;
@@ -33,6 +35,7 @@ typedef struct {
hb_tag_t chosen_script[2];
FeatureInfo infos[MAX_FEATURES];
unsigned info_count, current_stage[2];
+ unsigned pause[2][MAX_STAGES]; /* what ends each stage: VEGA_PAUSE_..., or 0 */
bool is_simple;
/* compiled */
FeatureMap features[MAX_FEATURES];
@@ -40,6 +43,7 @@ typedef struct {
uint32_t global_mask;
LookupMap *lookups[2];
unsigned lookup_count[2];
+ unsigned stage_first[2][MAX_STAGES], stage_end[2][MAX_STAGES]; /* each stage's lookups */
} Map;
static const hb_tag_t table_tags[2] = {HB_OT_TAG_GSUB, HB_OT_TAG_GPOS};
@@ -51,7 +55,10 @@ static void add_feature(Map *m, hb_tag_t tag, unsigned flags, unsigned value) {
m->info_count++;
}
static void enable_feature(Map *m, hb_tag_t tag, unsigned flags, unsigned value) { add_feature(m, tag, flags | F_GLOBAL, value); }
-static void add_gsub_pause(Map *m) { m->current_stage[0]++; }
+static void add_gsub_pause(Map *m, unsigned pause) {
+ if (m->current_stage[0] < MAX_STAGES) m->pause[0][m->current_stage[0]] = pause;
+ m->current_stage[0]++;
+}
static bool has_feature(const Map *m, hb_tag_t tag) {
for (int t = 0; t < 2; ++t)
@@ -97,6 +104,17 @@ static uint32_t one_mask(const Map *m, const char *tag) {
return f ? f->one_mask : 0;
}
+static bool push_lookup(Map *m, int table, LookupMap l, unsigned *allocated) {
+ if (m->lookup_count[table] == *allocated) {
+ *allocated = *allocated ? 2 * *allocated : 64;
+ LookupMap *grown = realloc(m->lookups[table], *allocated * sizeof(*grown));
+ if (!grown) return false;
+ m->lookups[table] = grown;
+ }
+ m->lookups[table][m->lookup_count[table]++] = l;
+ return true;
+}
+
static bool add_lookups(Map *m, int table, unsigned feature_index, uint32_t mask, bool auto_zwnj, bool auto_zwj,
bool random, bool per_syllable, unsigned *allocated) {
unsigned table_lookup_count = hb_ot_layout_table_get_lookup_count(m->face, table_tags[table]);
@@ -104,17 +122,11 @@ static bool add_lookups(Map *m, int table, unsigned feature_index, uint32_t mask
do {
len = 32;
hb_ot_layout_feature_get_lookups(m->face, table_tags[table], feature_index, offset, &len, indices);
- for (unsigned i = 0; i < len; ++i) {
- if (indices[i] >= table_lookup_count) continue;
- if (m->lookup_count[table] == *allocated) {
- *allocated = *allocated ? 2 * *allocated : 64;
- LookupMap *grown = realloc(m->lookups[table], *allocated * sizeof(*grown));
- if (!grown) return false;
- m->lookups[table] = grown;
- }
- m->lookups[table][m->lookup_count[table]++] =
- (LookupMap){indices[i], mask, auto_zwnj, auto_zwj, random, per_syllable};
- }
+ for (unsigned i = 0; i < len; ++i)
+ if (indices[i] < table_lookup_count &&
+ !push_lookup(m, table, (LookupMap){indices[i], mask, auto_zwnj, auto_zwj, random, per_syllable, 0},
+ allocated))
+ return false;
offset += len;
} while (len == 32);
return true;
@@ -254,9 +266,9 @@ static bool compile_map(Map *m) {
map->one_mask = (1u << map->shift) & map->mask;
}
if (m->is_simple) qsort(m->features, m->feature_count, sizeof(*m->features), by_tag);
- /* The final pause, then each stage's lookups: sorted, duplicates merged. */
- m->current_stage[0]++;
- m->current_stage[1]++;
+ /* The final pause, then each stage's lookups: sorted, duplicates merged,
+ * the stage's pause after them. */
+ if (++m->current_stage[0] > MAX_STAGES || ++m->current_stage[1] > MAX_STAGES) return false;
for (int t = 0; t < 2; ++t) {
unsigned allocated = 0, last = 0;
for (unsigned stage = 0; stage < m->current_stage[t]; ++stage) {
@@ -284,6 +296,12 @@ static bool compile_map(Map *m) {
}
m->lookup_count[t] = j + 1;
}
+ m->stage_first[t][stage] = last;
+ m->stage_end[t][stage] = m->lookup_count[t];
+ if (m->pause[t][stage] &&
+ !push_lookup(m, t, (LookupMap){VEGA_STEP_PAUSE, 0, false, false, false, false, m->pause[t][stage]},
+ &allocated))
+ return false;
last = m->lookup_count[t];
}
}
@@ -316,9 +334,9 @@ static int categorize(uint32_t script, hb_tag_t gsub_script) {
if (script == TAG("Syrc") || script == TAG("Thai") || script == TAG("Laoo") || script == TAG("Khmr") ||
script == TAG("Qaag"))
return -1;
- if (listed(indic_scripts, sizeof(indic_scripts) / sizeof(*indic_scripts), script) ||
- listed(use_scripts, sizeof(use_scripts) / sizeof(*use_scripts), script))
- return dflt ? VEGA_SHAPER_DEFAULT : -1;
+ if (listed(indic_scripts, sizeof(indic_scripts) / sizeof(*indic_scripts), script))
+ return dflt ? VEGA_SHAPER_DEFAULT : (gsub_script & 0xFF) == '3' ? -1 : VEGA_SHAPER_INDIC;
+ if (listed(use_scripts, sizeof(use_scripts) / sizeof(*use_scripts), script)) return dflt ? VEGA_SHAPER_DEFAULT : -1;
if (script == TAG("Mymr")) return dflt || gsub_script == TAG("mymr") ? VEGA_SHAPER_DEFAULT : -1;
return VEGA_SHAPER_DEFAULT;
}
@@ -330,14 +348,26 @@ static bool has_table(hb_face_t *face, hb_tag_t tag) {
return has;
}
+typedef struct { unsigned first, count; } Stage;
typedef struct {
uint32_t script;
int direction, native, shaper, flags;
uint32_t global_mask, kern_mask, masks[VEGA_PLAN_MASKS];
LookupMap *lookups[2];
unsigned lookup_count[2];
+ unsigned shaper_flags;
+ Stage stages[VEGA_PLAN_STAGES];
} Plan;
+/* The GSUB stage a feature is in (hb_ot_map_t::get_stage_lookups), for a
+ * shaper's would-substitute questions. */
+static Stage stage_of(const Map *m, const char *tag) {
+ const FeatureMap *f = find_feature(m, TAG(tag));
+ if (!f || f->stage[0] >= m->current_stage[0]) return (Stage){0, 0};
+ unsigned first = m->stage_first[0][f->stage[0]];
+ return (Stage){first, m->stage_end[0][f->stage[0]] - first};
+}
+
/* What HarfBuzz makes of the font's kern table (hb-ot-kern-table.hh): one
* with subtables that pass its checks (data), Apple's state machines among
* them (machine), cross-stream kerning (cross); and whether it has what
@@ -415,7 +445,7 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
/* hb_ot_shape_collect_features */
m->is_simple = true;
enable_feature(m, TAG("rvrn"), F_NONE, 1);
- add_gsub_pause(m);
+ add_gsub_pause(m, 0);
if (direction == VEGA_SHAPE_LTR) {
enable_feature(m, TAG("ltra"), F_NONE, 1);
enable_feature(m, TAG("ltrm"), F_NONE, 1);
@@ -434,23 +464,57 @@ 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); /* record_stch */
+ add_gsub_pause(m, 0); /* record_stch */
enable_feature(m, TAG("ccmp"), F_MANUAL_ZWJ, 1);
enable_feature(m, TAG("locl"), F_MANUAL_ZWJ, 1);
- add_gsub_pause(m);
+ add_gsub_pause(m, 0);
for (int i = 0; i < 7; ++i) {
bool syriac = arabic_features[i][3] == '2' || arabic_features[i][3] == '3';
add_feature(m, TAG(arabic_features[i]), F_MANUAL_ZWJ | (script == HB_SCRIPT_ARABIC && !syriac ? F_HAS_FALLBACK : 0), 1);
- add_gsub_pause(m);
+ 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); /* arabic_fallback_shape */
+ if (script == HB_SCRIPT_ARABIC) add_gsub_pause(m, 0); /* arabic_fallback_shape */
enable_feature(m, TAG("calt"), F_MANUAL_ZWJ, 1);
- if (!has_feature(m, TAG("rclt"))) add_gsub_pause(m);
+ if (!has_feature(m, TAG("rclt"))) add_gsub_pause(m, 0);
enable_feature(m, TAG("liga"), F_MANUAL_ZWJ, 1);
enable_feature(m, TAG("clig"), F_MANUAL_ZWJ, 1);
enable_feature(m, TAG("mset"), F_MANUAL_ZWJ, 1);
}
+ /* hb-ot-shaper-indic.cc: syllables found before any lookup; locl and
+ * ccmp; the initial reordering; the basic features one stage each; the
+ * final reordering; the rest. */
+ static const struct { const char *tag; unsigned flags; } indic_features[17] = {
+ {"nukt", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"akhn", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"rphf", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"rkrf", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"pref", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"blwf", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"abvf", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"half", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"pstf", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"vatu", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"cjct", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"init", F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"pres", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"abvs", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"blws", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"psts", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE},
+ {"haln", F_GLOBAL | F_MANUAL_ZWNJ | F_MANUAL_ZWJ | F_PER_SYLLABLE}};
+ if (shaper == VEGA_SHAPER_INDIC) {
+ m->is_simple = false;
+ add_gsub_pause(m, VEGA_PAUSE_SYLLABLES);
+ enable_feature(m, TAG("locl"), F_PER_SYLLABLE, 1);
+ enable_feature(m, TAG("ccmp"), F_PER_SYLLABLE, 1);
+ add_gsub_pause(m, VEGA_PAUSE_INITIAL_REORDERING);
+ for (int i = 0; i < 11; ++i) {
+ add_feature(m, TAG(indic_features[i].tag), indic_features[i].flags, 1);
+ add_gsub_pause(m, 0);
+ }
+ add_gsub_pause(m, VEGA_PAUSE_FINAL_REORDERING);
+ for (int i = 11; i < 17; ++i) add_feature(m, TAG(indic_features[i].tag), indic_features[i].flags, 1);
+ }
if (shaper == VEGA_SHAPER_HANGUL) {
m->is_simple = false;
add_feature(m, TAG("ljmo"), F_NONE, 1);
@@ -464,9 +528,14 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
add_feature(m, TAG(common[i]), F_GLOBAL | (i == 4 || i == 5 ? F_MANUAL_ZWNJ | F_MANUAL_ZWJ : 0), 1);
static const char *const horizontal[] = {"calt", "clig", "curs", "dist", "kern", "liga", "rclt"};
for (int i = 0; i < 7; ++i) add_feature(m, TAG(horizontal[i]), F_GLOBAL | (i == 4 ? F_HAS_FALLBACK : 0), 1);
- /* Hangul turns calt off: fonts put jamo lookups there. */
+ /* Hangul turns calt off: fonts put jamo lookups there. Indic turns liga
+ * off, and is done with syllables after GSUB. */
if (shaper == VEGA_SHAPER_HANGUL) add_feature(m, TAG("calt"), F_GLOBAL, 0);
- if (!compile_map(m)) FAIL("out of memory");
+ if (shaper == VEGA_SHAPER_INDIC) {
+ add_feature(m, TAG("liga"), F_GLOBAL, 0);
+ add_gsub_pause(m, VEGA_PAUSE_CLEAR_SYLLABLES);
+ }
+ if (!compile_map(m)) FAIL("out of memory, or more than %d stages", MAX_STAGES);
/* hb_ot_shape_planner_t::compile */
const FeatureMap *kern = find_feature(m, TAG("kern"));
@@ -476,8 +545,9 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
bool apply_kern = (!has_gpos_kern || !apply_gpos) && kern_table.data;
if (apply_kern && (kern_table.other || kern_table.cross))
FAIL("%s: the font's kern table has more than pairs (state machines, classes, cross-stream kerning)", name);
- /* Hangul's shaper neither zeroes marks nor places them without GPOS. */
- bool script_marks = shaper != VEGA_SHAPER_HANGUL;
+ /* Hangul's and Indic's shapers neither zero marks nor place them
+ * without GPOS. */
+ bool script_marks = shaper != VEGA_SHAPER_HANGUL && shaper != VEGA_SHAPER_INDIC;
bool adjust = !apply_gpos && (!apply_kern || !kern_table.cross), fallback_marks = adjust && script_marks;
if (shaper == VEGA_SHAPER_ARABIC) {
if (one_mask(m, "stch")) FAIL("%s: the font stretches (stch)", name);
@@ -499,15 +569,26 @@ static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *
(apply_kern ? VEGA_PLAN_KERN : 0) | (fallback_marks ? VEGA_PLAN_FALLBACK_MARKS : 0) |
(adjust ? VEGA_PLAN_ADJUST_ZEROING : 0),
m->global_mask, kern ? kern->one_mask : 0, {0}, {m->lookups[0], m->lookups[1]},
- {m->lookup_count[0], m->lookup_count[1]}};
- for (int i = 0; i < 7; ++i) plan->masks[VEGA_MASK_ISOL + i] = one_mask(m, arabic_features[i]);
+ {m->lookup_count[0], m->lookup_count[1]}, 0, {{0, 0}}};
plan->masks[VEGA_MASK_RTLM] = one_mask(m, "rtlm");
plan->masks[VEGA_MASK_FRAC] = frac;
plan->masks[VEGA_MASK_NUMR] = numr;
plan->masks[VEGA_MASK_DNOM] = dnom;
- plan->masks[VEGA_MASK_LJMO] = one_mask(m, "ljmo");
- plan->masks[VEGA_MASK_VJMO] = one_mask(m, "vjmo");
- plan->masks[VEGA_MASK_TJMO] = one_mask(m, "tjmo");
+ if (shaper == VEGA_SHAPER_ARABIC)
+ for (int i = 0; i < 7; ++i) plan->masks[VEGA_MASK_ISOL + i] = one_mask(m, arabic_features[i]);
+ if (shaper == VEGA_SHAPER_HANGUL) {
+ plan->masks[VEGA_MASK_LJMO] = one_mask(m, "ljmo");
+ plan->masks[VEGA_MASK_VJMO] = one_mask(m, "vjmo");
+ plan->masks[VEGA_MASK_TJMO] = one_mask(m, "tjmo");
+ }
+ if (shaper == VEGA_SHAPER_INDIC) {
+ /* the masks of the features set glyph by glyph (not global ones) */
+ static const char *const masked[] = {"rphf", "pref", "blwf", "abvf", "half", "pstf", "init"};
+ for (int i = 0; i < 7; ++i) plan->masks[VEGA_MASK_RPHF + i] = one_mask(m, masked[i]);
+ static const char *const staged[VEGA_PLAN_STAGES] = {"rphf", "pref", "blwf", "pstf", "vatu"};
+ for (int i = 0; i < VEGA_PLAN_STAGES; ++i) plan->stages[i] = stage_of(m, staged[i]);
+ if ((m->chosen_script[0] & 0xFF) != '2') plan->shaper_flags |= VEGA_INDIC_OLD_SPEC;
+ }
free(m);
return true;
fail:
@@ -661,6 +742,13 @@ bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t scrip
put32(&o, p->lookup_count[t]);
step += p->lookup_count[t];
}
+ put32(&o, p->shaper_flags);
+ for (int k = 0; k < VEGA_PLAN_STAGES; ++k) {
+ put16(&o, p->stages[k].first);
+ put16(&o, p->stages[k].count);
+ put32(&o, 0);
+ }
+ put32(&o, 0);
}
set32(&o, 56, step);
set32(&o, 60, (uint32_t)o.size);
@@ -669,10 +757,11 @@ bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t scrip
for (unsigned k = 0; k < plans[i].lookup_count[t]; ++k) {
const LookupMap *l = &plans[i].lookups[t][k];
put16(&o, l->index);
- unsigned char flags[2] = {(unsigned char)((l->auto_zwnj ? VEGA_STEP_AUTO_ZWNJ : 0) |
- (l->auto_zwj ? VEGA_STEP_AUTO_ZWJ : 0) |
- (l->random ? VEGA_STEP_RANDOM : 0) |
- (l->per_syllable ? VEGA_STEP_PER_SYLLABLE : 0)),
+ unsigned char flags[2] = {(unsigned char)(l->index == VEGA_STEP_PAUSE ? l->pause
+ : (l->auto_zwnj ? VEGA_STEP_AUTO_ZWNJ : 0) |
+ (l->auto_zwj ? VEGA_STEP_AUTO_ZWJ : 0) |
+ (l->random ? VEGA_STEP_RANDOM : 0) |
+ (l->per_syllable ? VEGA_STEP_PER_SYLLABLE : 0)),
0};
put(&o, flags, 2);
put32(&o, l->mask);
@@ -708,7 +797,8 @@ bool vega_shaping_compile(hb_face_t *face, const uint32_t *scripts, size_t scrip
if (hb_font_get_nominal_glyph(font, u, &g)) hb_set_add(glyphs, g);
}
for (size_t i = 0; i < plan_count; ++i)
- for (unsigned k = 0; k < plans[i].lookup_count[0]; ++k) hb_set_add(lookups, plans[i].lookups[0][k].index);
+ for (unsigned k = 0; k < plans[i].lookup_count[0]; ++k)
+ if (plans[i].lookups[0][k].index != VEGA_STEP_PAUSE) hb_set_add(lookups, plans[i].lookups[0][k].index);
hb_ot_layout_lookups_substitute_closure(face, lookups, glyphs);
hb_set_destroy(wanted);
hb_set_destroy(lookups);
@@ -763,6 +853,46 @@ static uint32_t jamo(Random *r, const Pool *p, uint32_t first, uint32_t count) {
return p->count && below(r, 2) ? pick(r, p) : first + below(r, count);
}
+/* An Indic syllable the ways text spells one (hb-ot-shaper-indic.cc):
+ * consonants joined by viramas (a joiner by some), a Ra and virama first
+ * for a reph, nuktas, matras, syllable modifiers, a virama at the end; a
+ * vowel with its modifiers. indic holds the script's consonants, Ra,
+ * viramas, nuktas, matras, modifiers and vowels. */
+enum { INDIC_CONSONANTS, INDIC_RA, INDIC_HALANTS, INDIC_NUKTAS, INDIC_MATRAS, INDIC_MODIFIERS, INDIC_VOWELS, INDIC_POOLS };
+static size_t indic_syllable(Random *r, const Pool *indic, uint32_t *text, size_t n) {
+ const Pool *c = &indic[INDIC_CONSONANTS], *ra = &indic[INDIC_RA], *h = &indic[INDIC_HALANTS],
+ *nukta = &indic[INDIC_NUKTAS], *m = &indic[INDIC_MATRAS], *sm = &indic[INDIC_MODIFIERS],
+ *v = &indic[INDIC_VOWELS];
+ if (!c->count || !h->count) return n;
+ if (ra->count && !below(r, 4)) {
+ text[n++] = pick(r, ra);
+ text[n++] = pick(r, h);
+ if (!below(r, 8)) text[n++] = 0x200D;
+ }
+ if (v->count && !below(r, 6)) {
+ text[n++] = pick(r, v);
+ if (nukta->count && !below(r, 8)) text[n++] = pick(r, nukta);
+ } else
+ for (unsigned k = 0, count = 1 + below(r, 3); k < count; ++k) {
+ text[n++] = ra->count && !below(r, 5) ? pick(r, ra) : pick(r, c);
+ if (nukta->count && !below(r, 6)) text[n++] = pick(r, nukta);
+ if (k + 1 == count) break;
+ unsigned joiner = below(r, 8);
+ if (joiner == 0) text[n++] = 0x200D;
+ text[n++] = pick(r, h);
+ if (joiner == 1) text[n++] = 0x200D;
+ if (joiner == 2) text[n++] = 0x200C;
+ }
+ if (m->count && below(r, 3)) {
+ text[n++] = pick(r, m);
+ if (!below(r, 8)) text[n++] = pick(r, m);
+ }
+ if (sm->count && !below(r, 4)) text[n++] = pick(r, sm);
+ if (!below(r, 10)) text[n++] = pick(r, h);
+ if (!below(r, 12)) text[n++] = below(r, 2) ? 0x200C : 0x200D;
+ return n;
+}
+
/* A Hangul syllable the ways text spells one (hb-ot-shaper-hangul.cc): jamo
* in the orders that compose (L V, L V T, LV T), a syllable whole, jamo in
* any order; now and then a tone mark after. jamo holds the font's L, V
@@ -805,8 +935,9 @@ void vega_shaping_check(hb_font_t *font, const VegaShaping *s, uint32_t script,
/* Letters of the script, marks it can take (its own and inherited
* ones), the rest of its characters and Common's (spaces, digits,
* punctuation), and those whose glyphs some substitution starts from. */
- Pool letters = {0}, marks = {0}, others = {0}, hot = {0}, jamo_pools[3] = {{0}};
+ Pool letters = {0}, marks = {0}, others = {0}, hot = {0}, jamo_pools[3] = {{0}}, indic[INDIC_POOLS] = {{0}};
bool hangul = script == HB_TAG('H', 'a', 'n', 'g');
+ bool syllabic = listed(indic_scripts, sizeof(indic_scripts) / sizeof(*indic_scripts), script);
hb_set_t *inputs = hb_set_create();
uint32_t common = HB_TAG('Z', 'y', 'y', 'y'), inherited = HB_TAG('Z', 'i', 'n', 'h');
for (unsigned t = 0; t < s->lookup_count[0]; ++t)
@@ -820,6 +951,19 @@ void vega_shaping_check(hb_font_t *font, const VegaShaping *s, uint32_t script,
if (hangul && ((code >= 0x1100 && code <= 0x115F) || (code >= 0xA960 && code <= 0xA97C))) pool_add(&jamo_pools[0], code);
if (hangul && ((code >= 0x1160 && code <= 0x11A7) || (code >= 0xD7B0 && code <= 0xD7C6))) pool_add(&jamo_pools[1], code);
if (hangul && ((code >= 0x11A8 && code <= 0x11FF) || (code >= 0xD7CB && code <= 0xD7FB))) pool_add(&jamo_pools[2], code);
+ if (syllabic && (of == script || of == inherited)) {
+ int pool = -1;
+ switch (vega_indic_categories(code) & 0xFF) {
+ case VEGA_OT_C: case VEGA_OT_CS: pool = INDIC_CONSONANTS; break;
+ case VEGA_OT_RA: pool = INDIC_RA; break;
+ case VEGA_OT_H: pool = INDIC_HALANTS; break;
+ case VEGA_OT_N: pool = INDIC_NUKTAS; break;
+ case VEGA_OT_M: case VEGA_OT_MPST: pool = INDIC_MATRAS; break;
+ case VEGA_OT_SM: case VEGA_OT_A: pool = INDIC_MODIFIERS; break;
+ case VEGA_OT_V: pool = INDIC_VOWELS; break;
+ }
+ if (pool >= 0) pool_add(&indic[pool], code);
+ }
if (mark && (of == script || of == inherited)) pool_add(&marks, code);
else if (!mark && of == script && c >= VEGA_LU && c <= VEGA_LO) pool_add(&letters, code);
else if (!mark && (of == script || of == common) && c != VEGA_ZL && c != VEGA_ZP) pool_add(&others, code);
@@ -851,6 +995,10 @@ void vega_shaping_check(hb_font_t *font, const VegaShaping *s, uint32_t script,
n = hangul_syllable(&random, jamo_pools, text, n);
continue;
}
+ if (syllabic && below(&random, 3)) {
+ n = indic_syllable(&random, indic, text, n);
+ continue;
+ }
text[n++] = hot.count && below(&random, 3) == 0 ? pick(&random, &hot) : pick(&random, &letters);
if (marks.count && below(&random, 100) < mark_rate) {
text[n++] = pick(&random, &marks);
@@ -916,4 +1064,5 @@ void vega_shaping_check(hb_font_t *font, const VegaShaping *s, uint32_t script,
free(others.items);
free(hot.items);
for (int k = 0; k < 3; ++k) free(jamo_pools[k].items);
+ for (int k = 0; k < INDIC_POOLS; ++k) free(indic[k].items);
}