aboutsummaryrefslogtreecommitdiffstats
path: root/text/shape.c
diff options
context:
space:
mode:
Diffstat (limited to 'text/shape.c')
-rw-r--r--text/shape.c872
1 files changed, 844 insertions, 28 deletions
diff --git a/text/shape.c b/text/shape.c
index 0813bae..3443b0b 100644
--- a/text/shape.c
+++ b/text/shape.c
@@ -1,15 +1,18 @@
-/* Shaping as HarfBuzz 10 does it (text/shape.h), with its default, Arabic
- * and Hebrew shapers. The code follows HarfBuzz's own step for step
- * (hb-ot-shape.cc, hb-ot-shape-normalize.cc, hb-buffer.cc, hb-ot-layout-
- * gsubgpos.hh, the GSUB and GPOS lookups, hb-ot-shaper-arabic.cc and
- * -hebrew.cc), under HarfBuzz's names, so that the two read side by side;
- * the build tool compares the results with HarfBuzz's (symbols-tool.c). */
+/* Shaping as HarfBuzz 10 does it (text/shape.h), with its default, Arabic,
+ * Hebrew, Hangul and Indic shapers. The code follows HarfBuzz's own step
+ * for step (hb-ot-shape.cc, hb-ot-shape-normalize.cc, hb-buffer.cc,
+ * hb-ot-layout-gsubgpos.hh, the GSUB and GPOS lookups, hb-ot-shaper-*.cc),
+ * under HarfBuzz's names, so that the two read side by side; the build
+ * tool compares the results with HarfBuzz's (symbols-tool.c). */
#include "shape.h"
+#include "syllabic.h"
#include "text.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#define TAG4(a, b, c, d) ((uint32_t)(a) << 24 | (uint32_t)(b) << 16 | (uint32_t)(c) << 8 | (uint32_t)(d))
+
/* ---- OpenType tables, read in place: a read past a table's end gives 0 */
static uint32_t get16(VegaTable t, uint32_t at) {
@@ -223,17 +226,22 @@ bool vega_shaping_open(VegaShaping *s, const unsigned char *bytes, size_t size,
REFUSE("out of memory");
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 + 72), le32(p + 76)}, {le32(p + 80), le32(p + 84)}};
- if ((p[4] != VEGA_SHAPE_LTR && p[4] != VEGA_SHAPE_RTL) || p[5] > VEGA_SHAPE_RTL || p[6] > VEGA_SHAPER_HANGUL)
+ uint32_t ranges[2][2] = {{le32(p + 80), le32(p + 84)}, {le32(p + 88), le32(p + 92)}};
+ if ((p[4] != VEGA_SHAPE_LTR && p[4] != VEGA_SHAPE_RTL) || p[5] > VEGA_SHAPE_RTL || p[6] > VEGA_SHAPER_INDIC)
REFUSE("plan %u", i);
if ((p[7] & VEGA_PLAN_FALLBACK_MARKS) && !s->extents) REFUSE("plan %u: marks placed without extents", i);
for (int table = 0; table < 2; ++table) {
if (ranges[table][0] > step_count || ranges[table][1] > step_count - ranges[table][0])
REFUSE("plan %u: steps", i);
- for (uint32_t k = 0; k < ranges[table][1]; ++k)
- if (le16(s->steps + (size_t)(ranges[table][0] + k) * VEGA_STEP_BYTES) >= s->lookup_count[table])
+ 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_CLEAR_SYLLABLES : le16(step) >= s->lookup_count[table])
REFUSE("plan %u: a lookup the font lacks", i);
+ }
}
+ for (int k = 0; k < VEGA_PLAN_STAGES; ++k)
+ if ((uint32_t)le16(p + 100 + 8 * k) + le16(p + 102 + 8 * k) > ranges[0][1]) REFUSE("plan %u: stages", i);
}
return true;
}
@@ -290,7 +298,13 @@ typedef struct {
uint32_t codepoint; /* a character, then (map_glyphs) a glyph */
uint32_t mask, cluster, glyph_index;
uint16_t uprops, gprops;
- uint8_t lig_props, action;
+ uint8_t lig_props;
+ union {
+ uint8_t action; /* Arabic's joining action, Hangul's jamo feature */
+ uint8_t position; /* Indic's */
+ };
+ uint8_t category; /* Indic's */
+ uint8_t syllable; /* serial << 4 | type, once a shaper finds syllables */
} Info;
typedef struct {
int32_t x_advance, y_advance, x_offset, y_offset;
@@ -311,7 +325,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_VARIATION_SELECTOR_FALLBACK = 32, HAS_BROKEN_SYLLABLE = 64 };
VegaShaper *vega_shaper_new(void) { return calloc(1, sizeof(VegaShaper)); }
@@ -390,6 +404,11 @@ static void output_glyph(VegaShaper *b, uint32_t glyph) {
b->out[b->out_len++].codepoint = glyph;
}
+static void output_info(VegaShaper *b, Info info) {
+ if (!ensure(b, b->out_len + 1)) return;
+ b->out[b->out_len++] = info;
+}
+
/* num_in glyphs become num_out, each a copy of the first with its own
* codepoint; their clusters merge. */
static void merge_clusters(VegaShaper *b, size_t start, size_t end);
@@ -674,12 +693,15 @@ static void form_clusters(VegaShaper *b) {
/* ---- The plan */
+typedef struct { uint32_t first, count; } Stage;
typedef struct {
uint32_t script;
int direction, native, shaper, flags;
uint32_t global_mask, kern_mask, masks[VEGA_PLAN_MASKS];
const unsigned char *gsub, *gpos;
uint32_t gsub_count, gpos_count;
+ unsigned shaper_flags;
+ Stage stages[VEGA_PLAN_STAGES];
} Plan;
static bool find_plan(const VegaShaping *s, uint32_t script, int direction, Plan *plan) {
@@ -691,9 +713,12 @@ static bool find_plan(const VegaShaping *s, uint32_t script, int direction, Plan
}
if (!found) return false;
*plan = (Plan){le32(found), found[4], found[5], found[6], found[7], le32(found + 8), le32(found + 12), {0},
- s->steps + (size_t)le32(found + 72) * VEGA_STEP_BYTES,
- s->steps + (size_t)le32(found + 80) * VEGA_STEP_BYTES, le32(found + 76), le32(found + 84)};
+ s->steps + (size_t)le32(found + 80) * VEGA_STEP_BYTES,
+ s->steps + (size_t)le32(found + 88) * VEGA_STEP_BYTES, le32(found + 84), le32(found + 92), found[96],
+ {{0, 0}}};
for (int k = 0; k < VEGA_PLAN_MASKS; ++k) plan->masks[k] = le32(found + 16 + 4 * k);
+ for (int k = 0; k < VEGA_PLAN_STAGES; ++k)
+ plan->stages[k] = (Stage){le16(found + 100 + 8 * k), le16(found + 102 + 8 * k)};
return true;
}
@@ -783,8 +808,23 @@ static bool compose_hebrew(const Normalizer *c, uint32_t a, uint32_t b, uint32_t
return *ab != 0;
}
+/* Indic's: no split matra composes again, and Bengali YA with nukta does,
+ * though Unicode excludes it. */
+static bool compose_indic(uint32_t a, uint32_t b, uint32_t *ab) {
+ if (is_mark_category(vega_category(a))) return false;
+ if (a == 0x09AF && b == 0x09BC) {
+ *ab = 0x09DF;
+ return true;
+ }
+ return compose_unicode(a, b, ab);
+}
+
static bool compose(const Normalizer *c, uint32_t a, uint32_t b, uint32_t *ab) {
- return c->plan->shaper == VEGA_SHAPER_HEBREW ? compose_hebrew(c, a, b, ab) : compose_unicode(a, b, ab);
+ switch (c->plan->shaper) {
+ case VEGA_SHAPER_HEBREW: return compose_hebrew(c, a, b, ab);
+ case VEGA_SHAPER_INDIC: return compose_indic(a, b, ab);
+ default: return compose_unicode(a, b, ab);
+ }
}
static bool nominal(const VegaShaping *s, uint32_t u, uint32_t *glyph) { return (*glyph = vega_shaping_glyph(s, u)) != 0; }
@@ -801,10 +841,17 @@ static void next_char(VegaShaper *b, uint32_t glyph) {
next_glyph(b);
}
+/* The shaper's decomposition: Indic keeps a few letters whole. */
+static bool decompose_char(const Normalizer *c, uint32_t ab, uint32_t *a, uint32_t *b) {
+ if (c->plan->shaper == VEGA_SHAPER_INDIC && (ab == 0x0931 || ab == 0x09DC || ab == 0x09DD || ab == 0x0B94))
+ return false;
+ return decompose_unicode(ab, a, b);
+}
+
/* Returns how many characters ab became, 0 if it did not decompose. */
static unsigned decompose(const Normalizer *c, bool shortest, uint32_t ab) {
uint32_t a, bb, a_glyph = 0, b_glyph = 0;
- if (!decompose_unicode(ab, &a, &bb) || (bb && !nominal(c->s, bb, &b_glyph))) return 0;
+ if (!decompose_char(c, ab, &a, &bb) || (bb && !nominal(c->s, bb, &b_glyph))) return 0;
bool has_a = nominal(c->s, a, &a_glyph);
if (shortest && has_a) {
output_char(c->b, a, a_glyph);
@@ -976,9 +1023,10 @@ static void normalize(const VegaShaping *s, const Plan *plan, VegaShaper *b) {
if (!b->len) return;
Normalizer c = {s, plan, b};
/* The shapers' preference is "auto" (composed diacritics); Hangul's is
- * none: what the font has stays, and nothing is composed again. */
+ * none: what the font has stays, and nothing is composed again;
+ * Indic's decomposes what decomposes even when the font has it whole. */
const bool none = plan->shaper == VEGA_SHAPER_HANGUL;
- const bool might_short_circuit = true, always_short_circuit = none;
+ const bool always_short_circuit = none, might_short_circuit = none || plan->shaper != VEGA_SHAPER_INDIC;
bool all_simple = true;
clear_output(b);
size_t count = b->len;
@@ -1237,6 +1285,7 @@ typedef struct {
uint32_t lookup_mask, lookup_props;
bool auto_zwnj, auto_zwj, random, has_glyph_classes;
int nesting_left;
+ bool per_syllable; /* GSUB matches within the syllable it starts in */
uint32_t random_state;
long last_base;
size_t last_base_until;
@@ -1271,6 +1320,7 @@ typedef struct {
Matcher matcher;
uint32_t glyph_data;
size_t idx, end;
+ uint8_t syllable; /* per syllable, from where the lookup is: only its glyphs match */
} Skippy;
enum { SKIP_NO, SKIP_YES, SKIP_MAYBE };
@@ -1278,9 +1328,11 @@ enum { MATCH_NO, MATCH_YES, MATCH_MAYBE };
enum { MATCH, NOT_MATCH, SKIP };
static void skippy_init(Skippy *it, Apply *c, bool context_match, size_t start) {
+ VegaShaper *b = c->b;
*it = (Skippy){c, c->lookup_props, context_match ? 0xFFFFFFFFu : c->lookup_mask,
c->table == 1 || (context_match && c->auto_zwnj), context_match || c->auto_zwj, c->table == 1,
- {MATCH_ANY, 0, 0}, 0, start, c->b->len};
+ {MATCH_ANY, 0, 0}, 0, start, b->len,
+ !c->table && c->per_syllable && start == b->idx && b->idx < b->len ? b->info[b->idx].syllable : 0};
}
static int may_skip(const Skippy *it, const Info *info) {
@@ -1302,7 +1354,7 @@ static bool matches(const Apply *c, const Matcher *m, uint32_t index, uint32_t g
}
static int may_match(const Skippy *it, const Info *info) {
- if (!(info->mask & it->mask)) return MATCH_NO;
+ if (!(info->mask & it->mask) || (it->syllable && it->syllable != info->syllable)) return MATCH_NO;
if (it->matcher.kind != MATCH_ANY) return matches(it->c, &it->matcher, it->glyph_data, info->codepoint) ? MATCH_YES : MATCH_NO;
return MATCH_MAYBE;
}
@@ -2105,13 +2157,23 @@ static VegaDigest buffer_digest(const VegaShaper *b) {
return d;
}
-static void apply_table(const VegaShaping *s, VegaShaper *b, int table, const unsigned char *steps, uint32_t count) {
+static bool shaper_pause(const VegaShaping *s, const Plan *plan, VegaShaper *b, unsigned pause);
+
+/* hb_ot_map_t::apply: the plan's lookups for a table in order; at a pause,
+ * the shaper's part (when it changed the buffer, the digest starts over). */
+static void apply_table(const VegaShaping *s, const Plan *plan, VegaShaper *b, int table) {
+ const unsigned char *steps = table ? plan->gpos : plan->gsub;
+ uint32_t count = table ? plan->gpos_count : plan->gsub_count;
/* A lookup can apply only where the buffer has a glyph it starts from. */
- Apply c = {s, b, table, table ? s->gpos : s->gsub, 0, 0, true, true, false, s->glyph_classes != 0,
- MAX_NESTING_LEVEL, 1, -1, 0, buffer_digest(b)};
+ Apply c = {.s = s, .b = b, .table = table, .t = table ? s->gpos : s->gsub, .has_glyph_classes = s->glyph_classes != 0,
+ .nesting_left = MAX_NESTING_LEVEL, .random_state = 1, .last_base = -1, .digest = buffer_digest(b)};
for (uint32_t i = 0; i < count; ++i) {
const unsigned char *step = steps + (size_t)i * VEGA_STEP_BYTES;
uint32_t index = le16(step);
+ if (index == VEGA_STEP_PAUSE) {
+ if (shaper_pause(s, plan, b, step[2])) c.digest = buffer_digest(b);
+ continue;
+ }
if (!digest_may_intersect(&s->lookups[table][index].digest, &c.digest)) continue;
c.lookup_mask = le32(step + 4);
c.last_base = -1;
@@ -2119,6 +2181,7 @@ static void apply_table(const VegaShaping *s, VegaShaper *b, int table, const un
c.auto_zwnj = step[2] & VEGA_STEP_AUTO_ZWNJ;
c.auto_zwj = step[2] & VEGA_STEP_AUTO_ZWJ;
c.random = step[2] & VEGA_STEP_RANDOM;
+ c.per_syllable = step[2] & VEGA_STEP_PER_SYLLABLE;
apply_string(&c, &s->lookups[table][index]);
}
}
@@ -2160,8 +2223,9 @@ static void kern_table(const VegaShaping *s, VegaShaper *b, const Plan *plan) {
if ((apple && (coverage & 0x20)) || !horizontal || format != 0) continue;
bool reverse = b->direction == VEGA_SHAPE_RTL;
if (reverse) reverse_range(b, 0, b->len, true);
- Apply c = {s, b, 1, s->gpos, plan->kern_mask, 0x08, true, true, false, s->glyph_classes != 0, MAX_NESTING_LEVEL, 1,
- -1, 0, {{0, 0, 0}}};
+ Apply c = {.s = s, .b = b, .table = 1, .t = s->gpos, .lookup_mask = plan->kern_mask, .lookup_props = 0x08,
+ .auto_zwnj = true, .auto_zwj = true, .has_glyph_classes = s->glyph_classes != 0,
+ .nesting_left = MAX_NESTING_LEVEL, .random_state = 1, .last_base = -1};
for (size_t idx = 0; idx < b->len;) {
if (!(b->info[idx].mask & plan->kern_mask)) {
idx++;
@@ -2385,6 +2449,756 @@ static void fallback_marks(const VegaShaping *s, const Plan *plan, VegaShaper *b
position_cluster(s, plan, b, start, b->len, adjust);
}
+/* ---- Would a lookup substitute glyphs as they are, alone
+ * (hb_ot_layout_lookup_would_substitute): the Indic shaper's question of
+ * its basic features. */
+
+static bool would_match_input(const Apply *c, Matcher m, const uint32_t *glyphs, unsigned len, unsigned count) {
+ if (count != len) return false;
+ for (unsigned i = 1; i < count; ++i)
+ if (!matches(c, &m, i - 1, glyphs[i])) return false;
+ return true;
+}
+
+static bool would_apply_rules(const Apply *c, int type, uint32_t set, Matcher input, const uint32_t *glyphs,
+ unsigned len, bool zero_context) {
+ VegaTable t = c->t;
+ for (uint32_t r = 0, n = set ? get16(t, set) : 0; r < n; ++r) {
+ uint32_t rule = offset16(t, set, set + 2 + 2 * r);
+ if (type == 5) {
+ input.values = rule + 4;
+ if (would_match_input(c, input, glyphs, len, get16(t, rule))) return true;
+ continue;
+ }
+ unsigned back_count = get16(t, rule);
+ uint32_t input_at = rule + 2 + 2 * back_count;
+ unsigned input_count = get16(t, input_at);
+ unsigned ahead_count = input_count ? get16(t, input_at + 2 + 2 * (input_count - 1)) : 0;
+ if (zero_context && (back_count || ahead_count)) continue;
+ input.values = input_at + 2;
+ if (would_match_input(c, input, glyphs, len, input_count)) return true;
+ }
+ return false;
+}
+
+static bool would_apply_subtable(const Apply *c, const VegaSubtable *st, const uint32_t *glyphs, unsigned len,
+ bool zero_context) {
+ VegaTable t = c->t;
+ uint32_t at = st->at, format = get16(t, at);
+ switch (st->type) {
+ case 1: case 2: case 3: case 8:
+ if (format != 1 && !(st->type == 1 && format == 2)) return false;
+ return len == 1 && coverage(t, offset16(t, at, at + 2), glyphs[0]) != NOT_COVERED;
+ case 4: {
+ uint32_t index = coverage(t, offset16(t, at, at + 2), glyphs[0]);
+ if (format != 1 || index == NOT_COVERED || index >= get16(t, at + 4)) return false;
+ uint32_t set = offset16(t, at, at + 6 + 2 * index);
+ for (uint32_t k = 0, n = set ? get16(t, set) : 0; k < n; ++k) {
+ uint32_t lig = offset16(t, set, set + 2 + 2 * k);
+ if (would_match_input(c, (Matcher){MATCH_GLYPH, 0, lig + 4}, glyphs, len, get16(t, lig + 2))) return true;
+ }
+ return false;
+ }
+ case 5: case 6: {
+ bool chain = st->type == 6;
+ if (format == 1 || format == 2) {
+ /* format 2 takes the first glyph's class whatever the coverage */
+ uint32_t classes = format == 2 ? offset16(t, at, at + (chain ? 6 : 4)) : 0;
+ uint32_t index = format == 1 ? coverage(t, offset16(t, at, at + 2), glyphs[0]) : class_of(t, classes, glyphs[0]);
+ uint32_t sets_at = format == 1 ? at + 4 : at + (chain ? 10 : 6);
+ if (index == NOT_COVERED || index >= get16(t, sets_at)) return false;
+ Matcher input = {format == 2 ? MATCH_CLASS : MATCH_GLYPH, classes, 0};
+ return would_apply_rules(c, st->type, offset16(t, at, sets_at + 2 + 2 * index), input, glyphs, len,
+ zero_context);
+ }
+ if (format != 3) return false;
+ if (!chain) return would_match_input(c, (Matcher){MATCH_COVERAGE, at, at + 8}, glyphs, len, get16(t, at + 2));
+ unsigned back_count = get16(t, at + 2);
+ uint32_t input_at = at + 4 + 2 * back_count;
+ unsigned input_count = get16(t, input_at);
+ unsigned ahead_count = get16(t, input_at + 2 + 2 * input_count);
+ if (zero_context && (back_count || ahead_count)) return false;
+ return would_match_input(c, (Matcher){MATCH_COVERAGE, at, input_at + 4}, glyphs, len, input_count);
+ }
+ default: return false;
+ }
+}
+
+/* Whether the lookups of a feature's stage (all of them, whatever their
+ * masks) would substitute glyphs, len of them. */
+static bool would_substitute(const VegaShaping *s, const Plan *plan, int stage, const uint32_t *glyphs, unsigned len,
+ bool zero_context) {
+ const Stage *st = &plan->stages[stage];
+ Apply c = {.s = s, .t = s->gsub};
+ for (uint32_t k = 0; k < st->count; ++k) {
+ const unsigned char *step = plan->gsub + (size_t)(st->first + k) * VEGA_STEP_BYTES;
+ uint32_t index = le16(step);
+ if (index == VEGA_STEP_PAUSE) continue;
+ const VegaLookup *l = &s->lookups[0][index];
+ if (!len || !digest_may_have(&l->digest, glyphs[0])) continue;
+ for (uint32_t i = 0; i < l->count; ++i)
+ if (would_apply_subtable(&c, &s->subtables[l->first + i], glyphs, len, zero_context)) return true;
+ }
+ return false;
+}
+
+/* ---- Syllables (hb-ot-shaper-*-machine.rl, hb-ot-shaper-syllabic.cc) */
+
+/* A shaper's machine over the buffer's categories: from each position the
+ * longest syllable it accepts; each numbered, 1 to 15 over again, beside
+ * its type. */
+static void find_syllables(VegaShaper *b, const VegaMachine *m) {
+ unsigned serial = 1;
+ for (size_t p = 0; p < b->len;) {
+ unsigned state = 0, type = 0;
+ size_t end = p + 1;
+ for (size_t q = p; q < b->len; ++q) {
+ size_t cell = (size_t)state * m->classes + m->class_of[b->info[q].category < 63 ? b->info[q].category : 63];
+ state = m->width == 1 ? ((const uint8_t *)m->next)[cell] : ((const uint16_t *)m->next)[cell];
+ if (state >= m->states) break;
+ if (m->accept[state]) type = m->accept[state], end = q + 1;
+ }
+ if (type == m->broken) b->scratch |= HAS_BROKEN_SYLLABLE;
+ for (size_t i = p; i < end; ++i) b->info[i].syllable = (uint8_t)(serial << 4 | (type ? type - 1 : 0));
+ if (++serial == 16) serial = 1;
+ p = end;
+ }
+}
+
+static size_t next_syllable(const VegaShaper *b, size_t start) {
+ size_t end = start + 1;
+ while (end < b->len && b->info[end].syllable == b->info[start].syllable) end++;
+ return end;
+}
+
+/* A dotted circle at the start of each broken syllable (after a repha that
+ * starts it), for its marks to sit on; false when none went in. */
+static bool insert_dotted_circles(const VegaShaping *s, VegaShaper *b, unsigned broken, unsigned category, int repha,
+ unsigned position) {
+ uint32_t glyph;
+ if (!(b->scratch & HAS_BROKEN_SYLLABLE) || !nominal(s, 0x25CC, &glyph)) return false;
+ /* All of it zero but these: HarfBuzz's is a fresh glyph_info. */
+ Info circle = {.codepoint = glyph, .uprops = VEGA_CC, .position = (uint8_t)position, .category = (uint8_t)category};
+ clear_output(b);
+ unsigned last = 0;
+ while (b->idx < b->len && b->successful) {
+ unsigned syllable = b->info[b->idx].syllable;
+ if (last != syllable && (syllable & 0x0F) == broken) {
+ last = syllable;
+ Info c = circle;
+ c.cluster = b->info[b->idx].cluster;
+ c.mask = b->info[b->idx].mask;
+ c.syllable = (uint8_t)syllable;
+ if (repha >= 0)
+ while (b->idx < b->len && b->successful && b->info[b->idx].syllable == last &&
+ b->info[b->idx].category == (unsigned)repha)
+ next_glyph(b);
+ output_info(b, c);
+ } else
+ next_glyph(b);
+ }
+ sync(b);
+ return true;
+}
+
+/* Vowel sequences that read as another vowel get a dotted circle between
+ * (hb-ot-shaper-vowel-constraints.cc, from the Universal Shaping Engine's
+ * IndicShapingInvalidCluster.txt): each first letter with what may not
+ * follow it. */
+static const struct { uint32_t script, first, second[12]; } vowel_constraints[] = {
+ {TAG4('D', 'e', 'v', 'a'), 0x0905, {0x093A, 0x093B, 0x093E, 0x0945, 0x0946, 0x0949, 0x094A, 0x094B, 0x094C, 0x094F, 0x0956, 0x0957}},
+ {TAG4('D', 'e', 'v', 'a'), 0x0906, {0x093A, 0x0945, 0x0946, 0x0947, 0x0948}},
+ {TAG4('D', 'e', 'v', 'a'), 0x0909, {0x0941}},
+ {TAG4('D', 'e', 'v', 'a'), 0x090F, {0x0945, 0x0946, 0x0947}},
+ {TAG4('B', 'e', 'n', 'g'), 0x0985, {0x09BE}},
+ {TAG4('B', 'e', 'n', 'g'), 0x098B, {0x09C3}},
+ {TAG4('B', 'e', 'n', 'g'), 0x098C, {0x09E2}},
+ {TAG4('G', 'u', 'r', 'u'), 0x0A05, {0x0A3E, 0x0A48, 0x0A4C}},
+ {TAG4('G', 'u', 'r', 'u'), 0x0A72, {0x0A3F, 0x0A40, 0x0A47}},
+ {TAG4('G', 'u', 'r', 'u'), 0x0A73, {0x0A41, 0x0A42, 0x0A4B}},
+ {TAG4('G', 'u', 'j', 'r'), 0x0A85, {0x0ABE, 0x0AC5, 0x0AC7, 0x0AC8, 0x0AC9, 0x0ACB, 0x0ACC}},
+ {TAG4('G', 'u', 'j', 'r'), 0x0AC5, {0x0ABE}},
+ {TAG4('O', 'r', 'y', 'a'), 0x0B05, {0x0B3E}},
+ {TAG4('O', 'r', 'y', 'a'), 0x0B0F, {0x0B57}},
+ {TAG4('O', 'r', 'y', 'a'), 0x0B13, {0x0B57}},
+ {TAG4('T', 'a', 'm', 'l'), 0x0B85, {0x0BC2}},
+ {TAG4('T', 'e', 'l', 'u'), 0x0C12, {0x0C4C, 0x0C55}},
+ {TAG4('T', 'e', 'l', 'u'), 0x0C3F, {0x0C55}},
+ {TAG4('T', 'e', 'l', 'u'), 0x0C46, {0x0C55}},
+ {TAG4('T', 'e', 'l', 'u'), 0x0C4A, {0x0C55}},
+ {TAG4('K', 'n', 'd', 'a'), 0x0C89, {0x0CBE}},
+ {TAG4('K', 'n', 'd', 'a'), 0x0C8B, {0x0CBE}},
+ {TAG4('K', 'n', 'd', 'a'), 0x0C92, {0x0CCC}},
+ {TAG4('M', 'l', 'y', 'm'), 0x0D07, {0x0D57}},
+ {TAG4('M', 'l', 'y', 'm'), 0x0D09, {0x0D57}},
+ {TAG4('M', 'l', 'y', 'm'), 0x0D0E, {0x0D46}},
+ {TAG4('M', 'l', 'y', 'm'), 0x0D12, {0x0D3E, 0x0D57}},
+ {TAG4('S', 'i', 'n', 'h'), 0x0D85, {0x0DCF, 0x0DD0, 0x0DD1}},
+ {TAG4('S', 'i', 'n', 'h'), 0x0D8B, {0x0DDF}},
+ {TAG4('S', 'i', 'n', 'h'), 0x0D8D, {0x0DD8}},
+ {TAG4('S', 'i', 'n', 'h'), 0x0D8F, {0x0DDF}},
+ {TAG4('S', 'i', 'n', 'h'), 0x0D91, {0x0DCA, 0x0DD9, 0x0DDA, 0x0DDC, 0x0DDD, 0x0DDE}},
+ {TAG4('S', 'i', 'n', 'h'), 0x0D94, {0x0DDF}},
+ {TAG4('B', 'r', 'a', 'h'), 0x11005, {0x11038}},
+ {TAG4('B', 'r', 'a', 'h'), 0x1100B, {0x1103E}},
+ {TAG4('B', 'r', 'a', 'h'), 0x1100F, {0x11042}},
+ {TAG4('K', 'h', 'o', 'j'), 0x11200, {0x1122C, 0x11231, 0x11233}},
+ {TAG4('K', 'h', 'o', 'j'), 0x11206, {0x1122C}},
+ {TAG4('K', 'h', 'o', 'j'), 0x1122C, {0x11230, 0x11231}},
+ {TAG4('K', 'h', 'o', 'j'), 0x11240, {0x1122E}},
+ {TAG4('S', 'i', 'n', 'd'), 0x112B0, {0x112E0, 0x112E5, 0x112E6, 0x112E7, 0x112E8}},
+ {TAG4('T', 'i', 'r', 'h'), 0x11481, {0x114B0}},
+ {TAG4('T', 'i', 'r', 'h'), 0x1148B, {0x114BA}},
+ {TAG4('T', 'i', 'r', 'h'), 0x1148D, {0x114BA}},
+ {TAG4('T', 'i', 'r', 'h'), 0x114AA, {0x114B5, 0x114B6}},
+ {TAG4('M', 'o', 'd', 'i'), 0x11600, {0x11639, 0x1163A}},
+ {TAG4('M', 'o', 'd', 'i'), 0x11601, {0x11639, 0x1163A}},
+ {TAG4('T', 'a', 'k', 'r'), 0x11680, {0x116AD, 0x116B4, 0x116B5}},
+ {TAG4('T', 'a', 'k', 'r'), 0x11686, {0x116B2}},
+};
+
+/* A dotted circle out before the current character, as a copy of it (its
+ * properties, less continuing the character before). */
+static void output_dotted_circle(VegaShaper *b) {
+ output_glyph(b, 0x25CC);
+ if (b->out_len) b->out[b->out_len - 1].uprops &= (uint16_t)~UPROPS_CONTINUATION;
+}
+
+static void preprocess_vowel_constraints(const Plan *plan, VegaShaper *b) {
+ bool any = false;
+ for (size_t k = 0; k < sizeof(vowel_constraints) / sizeof(*vowel_constraints) && !any; ++k)
+ any = vowel_constraints[k].script == plan->script;
+ if (!any && plan->script != TAG4('D', 'e', 'v', 'a')) return;
+ clear_output(b);
+ while (b->idx + 1 < b->len && b->successful) {
+ uint32_t u = b->info[b->idx].codepoint, next = b->info[b->idx + 1].codepoint;
+ bool matched = false;
+ for (size_t k = 0; k < sizeof(vowel_constraints) / sizeof(*vowel_constraints) && !matched; ++k)
+ if (vowel_constraints[k].script == plan->script && vowel_constraints[k].first == u)
+ for (int j = 0; j < 12 && vowel_constraints[k].second[j] && !matched; ++j)
+ matched = vowel_constraints[k].second[j] == next;
+ /* Devanagari's RA, virama, I: the circle goes before the I */
+ if (plan->script == TAG4('D', 'e', 'v', 'a') && u == 0x0930 && next == 0x094D && b->idx + 2 < b->len &&
+ b->info[b->idx + 2].codepoint == 0x0907) {
+ next_glyph(b);
+ next_glyph(b);
+ output_dotted_circle(b);
+ }
+ next_glyph(b);
+ if (matched) {
+ output_dotted_circle(b);
+ next_glyph(b);
+ }
+ }
+ sync(b);
+}
+
+/* ---- Indic (hb-ot-shaper-indic.cc): each consonant syllable's base found,
+ * its pre-base matras, reph and the like moved around the base and masked
+ * for the basic features; after those, the matras, reph and
+ * pre-base-reordering consonants placed again by what the features made. */
+
+enum { REPH_IMPLICIT, REPH_EXPLICIT, REPH_LOG_REPHA };
+typedef struct { uint32_t script, virama; uint8_t reph_pos, reph_mode; bool blwf_post_only; } IndicConfig;
+static const IndicConfig indic_configs[] = {
+ {0, 0, VEGA_POS_BEFORE_POST, REPH_IMPLICIT, false},
+ {TAG4('D', 'e', 'v', 'a'), 0x094D, VEGA_POS_BEFORE_POST, REPH_IMPLICIT, false},
+ {TAG4('B', 'e', 'n', 'g'), 0x09CD, VEGA_POS_AFTER_SUB, REPH_IMPLICIT, false},
+ {TAG4('G', 'u', 'r', 'u'), 0x0A4D, VEGA_POS_BEFORE_SUB, REPH_IMPLICIT, false},
+ {TAG4('G', 'u', 'j', 'r'), 0x0ACD, VEGA_POS_BEFORE_POST, REPH_IMPLICIT, false},
+ {TAG4('O', 'r', 'y', 'a'), 0x0B4D, VEGA_POS_AFTER_MAIN, REPH_IMPLICIT, false},
+ {TAG4('T', 'a', 'm', 'l'), 0x0BCD, VEGA_POS_AFTER_POST, REPH_IMPLICIT, false},
+ {TAG4('T', 'e', 'l', 'u'), 0x0C4D, VEGA_POS_AFTER_POST, REPH_EXPLICIT, true},
+ {TAG4('K', 'n', 'd', 'a'), 0x0CCD, VEGA_POS_AFTER_POST, REPH_IMPLICIT, true},
+ {TAG4('M', 'l', 'y', 'm'), 0x0D4D, VEGA_POS_AFTER_MAIN, REPH_LOG_REPHA, false},
+};
+
+typedef struct {
+ const VegaShaping *s;
+ const Plan *plan;
+ const IndicConfig *config;
+ bool old_spec, zero_context;
+ uint32_t virama; /* its glyph; 0 when the font has none */
+} Indic;
+
+static Indic indic_of(const VegaShaping *s, const Plan *plan) {
+ Indic d = {s, plan, &indic_configs[0], plan->shaper_flags & VEGA_INDIC_OLD_SPEC, false, 0};
+ for (size_t i = 1; i < sizeof(indic_configs) / sizeof(*indic_configs); ++i)
+ if (indic_configs[i].script == plan->script) d.config = &indic_configs[i];
+ /* New specifications match the basic features without context; so do
+ * scripts with one, but Malayalam's both take context. */
+ d.zero_context = !d.old_spec && plan->script != TAG4('M', 'l', 'y', 'm');
+ if (d.config->virama) nominal(s, d.config->virama, &d.virama);
+ return d;
+}
+
+#define FLAG(x) (1u << (x))
+#define FLAG_UNSAFE(x) ((x) < 32 ? 1u << (x) : 0u)
+static bool is_one_of(const Info *i, uint32_t flags) {
+ return !(i->gprops & LIGATED) && (FLAG_UNSAFE(i->category) & flags); /* a ligature is none of them */
+}
+static bool is_consonant(const Info *i) {
+ return is_one_of(i, FLAG(VEGA_OT_C) | FLAG(VEGA_OT_CS) | FLAG(VEGA_OT_RA) | FLAG(VEGA_OT_CM) | FLAG(VEGA_OT_V) |
+ FLAG(VEGA_OT_PLACEHOLDER) | FLAG(VEGA_OT_DOTTEDCIRCLE));
+}
+static bool is_joiner(const Info *i) { return is_one_of(i, FLAG(VEGA_OT_ZWJ) | FLAG(VEGA_OT_ZWNJ)); }
+static bool is_halant(const Info *i) { return is_one_of(i, FLAG(VEGA_OT_H)); }
+static bool ligated_and_didnt_multiply(const Info *i) { return (i->gprops & LIGATED) && !(i->gprops & MULTIPLIED); }
+
+static bool indic_would(const Indic *d, int stage, const uint32_t *glyphs, unsigned len) {
+ return would_substitute(d->s, d->plan, stage, glyphs, len, d->zero_context);
+}
+
+/* Below-base, post-base or base: what the font's features would make of
+ * the consonant with a virama, either way round (fonts copied old lookups
+ * into new scripts). */
+static uint8_t consonant_position(const Indic *d, uint32_t consonant) {
+ uint32_t glyphs[3] = {d->virama, consonant, d->virama};
+ if (indic_would(d, VEGA_STAGE_BLWF, glyphs, 2) || indic_would(d, VEGA_STAGE_BLWF, glyphs + 1, 2) ||
+ indic_would(d, VEGA_STAGE_VATU, glyphs, 2) || indic_would(d, VEGA_STAGE_VATU, glyphs + 1, 2))
+ return VEGA_POS_BELOW_C;
+ if (indic_would(d, VEGA_STAGE_PSTF, glyphs, 2) || indic_would(d, VEGA_STAGE_PSTF, glyphs + 1, 2) ||
+ indic_would(d, VEGA_STAGE_PREF, glyphs, 2) || indic_would(d, VEGA_STAGE_PREF, glyphs + 1, 2))
+ return VEGA_POS_POST_C;
+ return VEGA_POS_BASE_C;
+}
+
+static void setup_masks_indic(VegaShaper *b) {
+ for (size_t i = 0; i < b->len; ++i) {
+ uint16_t categories = vega_indic_categories(b->info[i].codepoint);
+ b->info[i].category = categories & 0xFF;
+ b->info[i].position = categories >> 8;
+ }
+}
+
+static void indic_reorder_consonant_syllable(const Indic *d, VegaShaper *b, size_t start, size_t end) {
+ Info *info = b->info;
+ const uint32_t *masks = d->plan->masks;
+ uint32_t script = d->plan->script;
+ /* Kannada's Ra, halant, ZWJ is Ra, ZWJ, halant. */
+ if (script == TAG4('K', 'n', 'd', 'a') && start + 3 <= end && is_one_of(&info[start], FLAG(VEGA_OT_RA)) &&
+ is_one_of(&info[start + 1], FLAG(VEGA_OT_H)) && is_one_of(&info[start + 2], FLAG(VEGA_OT_ZWJ))) {
+ merge_clusters(b, start + 1, start + 3);
+ Info t = info[start + 1];
+ info[start + 1] = info[start + 2];
+ info[start + 2] = t;
+ }
+
+ /* 1. The base: from the end back, the first consonant without a
+ * below-base or post-base form (those follow below-base ones), or the
+ * first consonant. An initial Ra, halant that the font makes a reph
+ * is no candidate. */
+ size_t base = end, limit = start;
+ bool has_reph = false;
+ if (masks[VEGA_MASK_RPHF] && start + 3 <= end &&
+ ((d->config->reph_mode == REPH_IMPLICIT && !is_joiner(&info[start + 2])) ||
+ (d->config->reph_mode == REPH_EXPLICIT && info[start + 2].category == VEGA_OT_ZWJ))) {
+ uint32_t glyphs[3] = {info[start].codepoint, info[start + 1].codepoint,
+ d->config->reph_mode == REPH_EXPLICIT ? info[start + 2].codepoint : 0};
+ if (indic_would(d, VEGA_STAGE_RPHF, glyphs, 2) ||
+ (d->config->reph_mode == REPH_EXPLICIT && indic_would(d, VEGA_STAGE_RPHF, glyphs, 3))) {
+ limit += 2;
+ while (limit < end && is_joiner(&info[limit])) limit++;
+ base = start;
+ has_reph = true;
+ }
+ } else if (d->config->reph_mode == REPH_LOG_REPHA && info[start].category == VEGA_OT_REPHA) {
+ limit += 1;
+ while (limit < end && is_joiner(&info[limit])) limit++;
+ base = start;
+ has_reph = true;
+ }
+ {
+ size_t i = end;
+ bool seen_below = false;
+ do {
+ i--;
+ if (is_consonant(&info[i])) {
+ if (info[i].position != VEGA_POS_BELOW_C && (info[i].position != VEGA_POS_POST_C || seen_below)) {
+ base = i;
+ break;
+ }
+ if (info[i].position == VEGA_POS_BELOW_C) seen_below = true;
+ base = i;
+ } else if (start < i && info[i].category == VEGA_OT_ZWJ && info[i - 1].category == VEGA_OT_H)
+ break; /* a ZWJ after a halant asks for a half form: the search stops */
+ } while (i > limit);
+ }
+ if (has_reph && base == start && limit - base <= 2) has_reph = false; /* Ra alone is the base */
+
+ /* 2 and 3 (matras decomposed, marks in canonical order) normalization
+ * did. Reorder: */
+ for (size_t i = start; i < base; ++i)
+ if (info[i].position > VEGA_POS_PRE_C) info[i].position = VEGA_POS_PRE_C;
+ if (base < end) info[base].position = VEGA_POS_BASE_C;
+ if (has_reph) info[start].position = VEGA_POS_RA_TO_BECOME_REPH;
+
+ /* Old specifications: the first post-base halant after the last
+ * consonant (Kannada not past a halant already there). */
+ if (d->old_spec) {
+ bool disallow_double_halants = script == TAG4('K', 'n', 'd', 'a');
+ for (size_t i = base + 1; i < end; ++i)
+ if (info[i].category == VEGA_OT_H) {
+ size_t j;
+ for (j = end - 1; j > i; j--)
+ if (is_consonant(&info[j]) || (disallow_double_halants && info[j].category == VEGA_OT_H)) break;
+ if (info[j].category != VEGA_OT_H && j > i) {
+ Info t = info[i];
+ memmove(&info[i], &info[i + 1], (j - i) * sizeof(Info));
+ info[j] = t;
+ }
+ break;
+ }
+ }
+
+ /* Joiners, nuktas, register shifters, medials and halants go with what
+ * is before them (a halant not with a left matra). */
+ {
+ uint8_t last_pos = VEGA_POS_START;
+ for (size_t i = start; i < end; ++i) {
+ if (FLAG_UNSAFE(info[i].category) & (FLAG(VEGA_OT_ZWJ) | FLAG(VEGA_OT_ZWNJ) | FLAG(VEGA_OT_N) |
+ FLAG(VEGA_OT_RS) | FLAG(VEGA_OT_CM) | FLAG(VEGA_OT_H))) {
+ info[i].position = last_pos;
+ if (info[i].category == VEGA_OT_H && info[i].position == VEGA_POS_PRE_M)
+ for (size_t j = i; j > start; j--)
+ if (info[j - 1].position != VEGA_POS_PRE_M) {
+ info[i].position = info[j - 1].position;
+ break;
+ }
+ } else if (info[i].position != VEGA_POS_SMVD) {
+ if (info[i].category == VEGA_OT_MPST && i > start && info[i - 1].category == VEGA_OT_SM)
+ info[i - 1].position = info[i].position;
+ last_pos = info[i].position;
+ }
+ }
+ }
+ /* A post-base consonant owns what is before it since the last
+ * consonant or matra. */
+ {
+ size_t last = base;
+ for (size_t i = base + 1; i < end; ++i)
+ if (is_consonant(&info[i])) {
+ for (size_t j = last + 1; j < i; ++j)
+ if (info[j].position < VEGA_POS_SMVD) info[j].position = info[i].position;
+ last = i;
+ } else if (FLAG_UNSAFE(info[i].category) & (FLAG(VEGA_OT_M) | FLAG(VEGA_OT_MPST)))
+ last = i;
+ }
+
+ {
+ /* Sorted by position, stably; syllable holds each glyph's place
+ * before, to merge the clusters of what moved past base. */
+ uint8_t syllable = info[start].syllable;
+ for (size_t i = start; i < end; ++i) info[i].syllable = (uint8_t)(i - start);
+ for (size_t i = start + 1; i < end; ++i) {
+ size_t j = i;
+ while (j > start && info[j - 1].position > info[i].position) j--;
+ if (j == i) continue;
+ Info t = info[i];
+ memmove(&info[j + 1], &info[j], (i - j) * sizeof(Info));
+ info[j] = t;
+ }
+ /* The base again; a run of left matras back in their order. */
+ size_t first_left_matra = end, last_left_matra = end;
+ base = end;
+ for (size_t i = start; i < end; ++i) {
+ if (info[i].position == VEGA_POS_BASE_C) {
+ base = i;
+ break;
+ }
+ if (info[i].position == VEGA_POS_PRE_M) {
+ if (first_left_matra == end) first_left_matra = i;
+ last_left_matra = i;
+ }
+ }
+ if (first_left_matra < last_left_matra) {
+ reverse_range(b, first_left_matra, last_left_matra + 1, false);
+ size_t i = first_left_matra;
+ for (size_t j = i; j <= last_left_matra; ++j)
+ if (FLAG_UNSAFE(info[j].category) & (FLAG(VEGA_OT_M) | FLAG(VEGA_OT_MPST))) {
+ reverse_range(b, i, j + 1, false);
+ i = j + 1;
+ }
+ }
+ if (d->old_spec || end - start > 127)
+ merge_clusters(b, base, end);
+ else
+ for (size_t i = base; i < end; ++i)
+ if (info[i].syllable != 255) {
+ size_t min = i, max = i, j = start + info[i].syllable;
+ while (j != i) {
+ if (j < min) min = j;
+ if (j > max) max = j;
+ size_t next = start + info[j].syllable;
+ info[j].syllable = 255;
+ j = next;
+ }
+ merge_clusters(b, base > min ? base : min, max + 1);
+ }
+ for (size_t i = start; i < end; ++i) info[i].syllable = syllable;
+ }
+
+ /* Masks: reph; pre-base (half, and blwf in new specifications that
+ * have it there); post-base. */
+ for (size_t i = start; i < end && info[i].position == VEGA_POS_RA_TO_BECOME_REPH; ++i)
+ info[i].mask |= masks[VEGA_MASK_RPHF];
+ uint32_t mask = masks[VEGA_MASK_HALF];
+ if (!d->old_spec && !d->config->blwf_post_only) mask |= masks[VEGA_MASK_BLWF];
+ for (size_t i = start; i < base; ++i) info[i].mask |= mask;
+ mask = masks[VEGA_MASK_BLWF] | masks[VEGA_MASK_ABVF] | masks[VEGA_MASK_PSTF];
+ for (size_t i = base + 1; i < end; ++i) info[i].mask |= mask;
+
+ /* Old-spec Devanagari: an eyelash Ra below a half form too, but not
+ * with a ZWJ. */
+ if (d->old_spec && script == TAG4('D', 'e', 'v', 'a'))
+ for (size_t i = start; i + 1 < base; ++i)
+ if (info[i].category == VEGA_OT_RA && info[i + 1].category == VEGA_OT_H &&
+ (i + 2 == base || info[i + 2].category != VEGA_OT_ZWJ)) {
+ info[i].mask |= masks[VEGA_MASK_BLWF];
+ info[i + 1].mask |= masks[VEGA_MASK_BLWF];
+ }
+
+ /* A halant, Ra the font reorders before the base: marked for pref. */
+ if (masks[VEGA_MASK_PREF] && base + 2 < end)
+ for (size_t i = base + 1; i + 1 < end; ++i) {
+ uint32_t glyphs[2] = {info[i].codepoint, info[i + 1].codepoint};
+ if (indic_would(d, VEGA_STAGE_PREF, glyphs, 2)) {
+ info[i].mask |= masks[VEGA_MASK_PREF];
+ info[i + 1].mask |= masks[VEGA_MASK_PREF];
+ break;
+ }
+ }
+
+ /* A ZWNJ keeps what is before it back to a consonant from half forms. */
+ for (size_t i = start + 1; i < end; ++i)
+ if (is_joiner(&info[i])) {
+ bool non_joiner = info[i].category == VEGA_OT_ZWNJ;
+ size_t j = i;
+ do {
+ j--;
+ if (non_joiner) info[j].mask &= ~masks[VEGA_MASK_HALF];
+ } while (j > start && !is_consonant(&info[j]));
+ }
+}
+
+static bool indic_initial_reordering(const Indic *d, VegaShaper *b) {
+ if (d->virama)
+ for (size_t i = 0; i < b->len; ++i)
+ if (b->info[i].position == VEGA_POS_BASE_C) b->info[i].position = consonant_position(d, b->info[i].codepoint);
+ bool changed = insert_dotted_circles(d->s, b, 4, VEGA_OT_DOTTEDCIRCLE, VEGA_OT_REPHA, VEGA_POS_END);
+ for (size_t start = 0, end; start < b->len; start = end) {
+ end = next_syllable(b, start);
+ /* Vowel syllables look like consonant ones, and broken ones have
+ * their dotted circle, like a standalone cluster's. */
+ unsigned type = b->info[start].syllable & 0x0F;
+ if (type <= 2 || type == 4) indic_reorder_consonant_syllable(d, b, start, end);
+ }
+ return changed;
+}
+
+/* Categories of the preceding character that keep a left matra from
+ * 'init': HarfBuzz's Cf to Mn. */
+static bool word_character(unsigned c) {
+ switch (c) {
+ case VEGA_CF: case VEGA_CN: case VEGA_CO: case VEGA_CS: case VEGA_LL: case VEGA_LM: case VEGA_LO: case VEGA_LT:
+ case VEGA_LU: case VEGA_MC: case VEGA_ME: case VEGA_MN:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void indic_final_syllable(const Indic *d, VegaShaper *b, size_t start, size_t end) {
+ Info *info = b->info;
+ const uint32_t *masks = d->plan->masks;
+ uint32_t script = d->plan->script;
+ bool malayalam_or_tamil = script == TAG4('M', 'l', 'y', 'm') || script == TAG4('T', 'a', 'm', 'l');
+ /* A virama that ligated and multiplied back out is a halant again. */
+ if (d->virama)
+ for (size_t i = start; i < end; ++i)
+ if (info[i].codepoint == d->virama && (info[i].gprops & LIGATED) && (info[i].gprops & MULTIPLIED)) {
+ info[i].category = VEGA_OT_H;
+ info[i].gprops &= (uint16_t)~(LIGATED | MULTIPLIED);
+ }
+
+ /* 4. The base again: where a pref candidate formed nothing, or (in
+ * Malayalam) past unformed below-base forms. */
+ bool try_pref = masks[VEGA_MASK_PREF] != 0;
+ size_t base;
+ for (base = start; base < end; base++)
+ if (info[base].position >= VEGA_POS_BASE_C) {
+ if (try_pref && base + 1 < end)
+ for (size_t i = base + 1; i < end; ++i)
+ if (info[i].mask & masks[VEGA_MASK_PREF]) {
+ if (!((info[i].gprops & SUBSTITUTED) && ligated_and_didnt_multiply(&info[i]))) {
+ base = i;
+ while (base < end && is_halant(&info[base])) base++;
+ if (base < end) info[base].position = VEGA_POS_BASE_C;
+ try_pref = false;
+ }
+ break;
+ }
+ if (script == TAG4('M', 'l', 'y', 'm'))
+ for (size_t i = base + 1; i < end; ++i) {
+ while (i < end && is_joiner(&info[i])) i++;
+ if (i == end || !is_halant(&info[i])) break;
+ i++;
+ while (i < end && is_joiner(&info[i])) i++;
+ if (i < end && is_consonant(&info[i]) && info[i].position == VEGA_POS_BELOW_C) {
+ base = i;
+ info[base].position = VEGA_POS_BASE_C;
+ }
+ }
+ if (start < base && info[base].position > VEGA_POS_BASE_C) base--;
+ break;
+ }
+ if (base == end && start < base && is_one_of(&info[base - 1], FLAG(VEGA_OT_ZWJ))) base--;
+ if (base < end)
+ while (start < base && is_one_of(&info[base], FLAG(VEGA_OT_N) | FLAG(VEGA_OT_H))) base--;
+
+ /* Pre-base matras: after the last halant before the base (not one a
+ * ZWJ follows, nor the matra's own), else where they are. */
+ if (start + 1 < end && start < base) {
+ size_t new_pos = base == end ? base - 2 : base - 1;
+ if (!malayalam_or_tamil) {
+ for (;;) {
+ while (new_pos > start &&
+ !is_one_of(&info[new_pos], FLAG(VEGA_OT_M) | FLAG(VEGA_OT_MPST) | FLAG(VEGA_OT_H)))
+ new_pos--;
+ if (is_halant(&info[new_pos]) && info[new_pos].position != VEGA_POS_PRE_M) {
+ if (new_pos + 1 < end && info[new_pos + 1].category == VEGA_OT_ZWJ && new_pos > start) {
+ new_pos--;
+ continue; /* keep searching */
+ }
+ } else
+ new_pos = start;
+ break;
+ }
+ }
+ if (start < new_pos && info[new_pos].position != VEGA_POS_PRE_M) {
+ for (size_t i = new_pos; i > start; i--)
+ if (info[i - 1].position == VEGA_POS_PRE_M) {
+ size_t old_pos = i - 1;
+ if (old_pos < base && base <= new_pos) base--;
+ Info t = info[old_pos];
+ memmove(&info[old_pos], &info[old_pos + 1], (new_pos - old_pos) * sizeof(Info));
+ info[new_pos] = t;
+ merge_clusters(b, new_pos, end < base + 1 ? end : base + 1);
+ new_pos--;
+ }
+ } else
+ for (size_t i = start; i < base; ++i)
+ if (info[i].position == VEGA_POS_PRE_M) {
+ merge_clusters(b, i, end < base + 1 ? end : base + 1);
+ break;
+ }
+ }
+
+ /* Reph: moved only if it formed (or, encoded as a repha, if it did
+ * not ligate), to where the script puts it. */
+ if (start + 1 < end && info[start].position == VEGA_POS_RA_TO_BECOME_REPH &&
+ ((info[start].category == VEGA_OT_REPHA) ^ ligated_and_didnt_multiply(&info[start]))) {
+ size_t new_reph_pos = start;
+ unsigned reph_pos = d->config->reph_pos;
+ bool found = false;
+ if (reph_pos != VEGA_POS_AFTER_POST) {
+ /* 2. after the first explicit halant before the base (and a
+ * joiner after it) */
+ new_reph_pos = start + 1;
+ while (new_reph_pos < base && !is_halant(&info[new_reph_pos])) new_reph_pos++;
+ if (new_reph_pos < base && is_halant(&info[new_reph_pos])) {
+ if (new_reph_pos + 1 < base && is_joiner(&info[new_reph_pos + 1])) new_reph_pos++;
+ found = true;
+ }
+ /* 3. after the main consonant */
+ if (!found && reph_pos == VEGA_POS_AFTER_MAIN) {
+ new_reph_pos = base;
+ while (new_reph_pos + 1 < end && info[new_reph_pos + 1].position <= VEGA_POS_AFTER_MAIN) new_reph_pos++;
+ found = new_reph_pos < end;
+ }
+ /* 4. before the first post-base consonant or matra */
+ if (!found && reph_pos == VEGA_POS_AFTER_SUB) {
+ new_reph_pos = base;
+ while (new_reph_pos + 1 < end &&
+ !(FLAG_UNSAFE(info[new_reph_pos + 1].position) &
+ (FLAG(VEGA_POS_POST_C) | FLAG(VEGA_POS_AFTER_POST) | FLAG(VEGA_POS_SMVD))))
+ new_reph_pos++;
+ found = new_reph_pos < end;
+ }
+ }
+ if (!found) {
+ /* 5. as step 2 */
+ new_reph_pos = start + 1;
+ while (new_reph_pos < base && !is_halant(&info[new_reph_pos])) new_reph_pos++;
+ if (new_reph_pos < base && is_halant(&info[new_reph_pos])) {
+ if (new_reph_pos + 1 < base && is_joiner(&info[new_reph_pos + 1])) new_reph_pos++;
+ found = true;
+ }
+ }
+ if (!found) {
+ /* 6. the end, before any syllable modifiers; before a halant
+ * that follows a matra */
+ new_reph_pos = end - 1;
+ while (new_reph_pos > start && info[new_reph_pos].position == VEGA_POS_SMVD) new_reph_pos--;
+ if (is_halant(&info[new_reph_pos]))
+ for (size_t i = base + 1; i < new_reph_pos; ++i)
+ if (FLAG_UNSAFE(info[i].category) & (FLAG(VEGA_OT_M) | FLAG(VEGA_OT_MPST))) new_reph_pos--;
+ }
+ merge_clusters(b, start, new_reph_pos + 1);
+ Info reph = info[start];
+ memmove(&info[start], &info[start + 1], (new_reph_pos - start) * sizeof(Info));
+ info[new_reph_pos] = reph;
+ if (start < base && base <= new_reph_pos) base--;
+ }
+
+ /* A pre-base-reordering consonant that pref formed: before the base,
+ * where a pre-base matra would go. */
+ if (try_pref && base + 1 < end)
+ for (size_t i = base + 1; i < end; ++i)
+ if (info[i].mask & masks[VEGA_MASK_PREF]) {
+ if (ligated_and_didnt_multiply(&info[i])) {
+ size_t new_pos = base;
+ if (!malayalam_or_tamil)
+ while (new_pos > start &&
+ !is_one_of(&info[new_pos - 1], FLAG(VEGA_OT_M) | FLAG(VEGA_OT_MPST) | FLAG(VEGA_OT_H)))
+ new_pos--;
+ if (new_pos > start && is_halant(&info[new_pos - 1]) && new_pos < end && is_joiner(&info[new_pos]))
+ new_pos++;
+ size_t old_pos = i;
+ merge_clusters(b, new_pos, old_pos + 1);
+ Info t = info[old_pos];
+ memmove(&info[new_pos + 1], &info[new_pos], (old_pos - new_pos) * sizeof(Info));
+ info[new_pos] = t;
+ if (new_pos <= base && base < old_pos) base++;
+ }
+ break;
+ }
+
+ /* 'init' on a left matra that starts a word. */
+ if (info[start].position == VEGA_POS_PRE_M && (!start || !word_character(gen_cat(&info[start - 1]))))
+ info[start].mask |= masks[VEGA_MASK_INDIC_INIT];
+}
+
+static void indic_final_reordering(const Indic *d, VegaShaper *b) {
+ for (size_t start = 0, end; start < b->len; start = end) {
+ end = next_syllable(b, start);
+ indic_final_syllable(d, b, start, end);
+ }
+}
+
+/* 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) {
+ if (plan->shaper != VEGA_SHAPER_INDIC) return false;
+ Indic d = indic_of(s, plan);
+ switch (pause) {
+ case VEGA_PAUSE_SYLLABLES: find_syllables(b, &vega_indic_machine); return false;
+ case VEGA_PAUSE_INITIAL_REORDERING: return indic_initial_reordering(&d, b);
+ case VEGA_PAUSE_FINAL_REORDERING: indic_final_reordering(&d, b); return false;
+ default: return false;
+ }
+}
+
/* ---- Positions */
static void fallback_spaces(const VegaShaping *s, VegaShaper *b) {
@@ -2449,7 +3263,7 @@ static void position(const VegaShaping *s, const Plan *plan, VegaShaper *b) {
for (size_t i = 0; i < b->len; ++i) b->pos[i].x_advance = advance_of(s, b->info[i].codepoint);
if (b->scratch & HAS_SPACE_FALLBACK) fallback_spaces(s, b);
bool adjust = (plan->flags & VEGA_PLAN_ADJUST_ZEROING) && b->direction == VEGA_SHAPE_LTR;
- if (plan->flags & VEGA_PLAN_GPOS) apply_table(s, b, 1, plan->gpos, plan->gpos_count);
+ if (plan->flags & VEGA_PLAN_GPOS) apply_table(s, plan, b, 1);
if (plan->flags & VEGA_PLAN_KERN) kern_table(s, b, plan);
if (plan->flags & VEGA_PLAN_ZERO_MARKS)
for (size_t i = 0; i < b->len; ++i)
@@ -2484,13 +3298,14 @@ long vega_shape(VegaShaper *b, const VegaShaping *s, const uint32_t *codes, size
b->max_len = count * 64 > 16384 ? count * 64 : 16384;
b->max_ops = (long)(count * 1024 > 16384 ? count * 1024 : 16384);
if (!ensure(b, count + 1)) return -1;
- for (size_t i = 0; i < count; ++i) b->info[i] = (Info){codes[i], plan.global_mask, (uint32_t)i, 0, 0, 0, 0, 0};
+ for (size_t i = 0; i < count; ++i) b->info[i] = (Info){.codepoint = codes[i], .mask = plan.global_mask, .cluster = (uint32_t)i};
b->len = count;
set_unicode_props(b);
form_clusters(b);
ensure_native_direction(b, &plan);
if (plan.shaper == VEGA_SHAPER_HANGUL) preprocess_hangul(s, b);
+ if (plan.shaper == VEGA_SHAPER_INDIC) preprocess_vowel_constraints(&plan, b);
/* Mirror what the font mirrors; mark the rest for rtlm. */
if (direction == VEGA_SHAPE_RTL)
@@ -2502,6 +3317,7 @@ long vega_shape(VegaShaper *b, const VegaShaping *s, const uint32_t *codes, size
normalize(s, &plan, b);
setup_masks_fraction(b, &plan);
if (plan.shaper == VEGA_SHAPER_ARABIC) setup_masks_arabic(b, &plan);
+ if (plan.shaper == VEGA_SHAPER_INDIC) setup_masks_indic(b);
if (plan.shaper == VEGA_SHAPER_HANGUL)
for (size_t i = 0; i < b->len; ++i)
if (b->info[i].action >= LJMO && b->info[i].action <= TJMO)
@@ -2517,7 +3333,7 @@ long vega_shape(VegaShaper *b, const VegaShaping *s, const uint32_t *codes, size
if (plan.flags & VEGA_PLAN_GUESS_CLASSES)
info->gprops = gen_cat(info) != VEGA_MN || is_default_ignorable(info) ? BASE_GLYPH : MARK;
}
- apply_table(s, b, 0, plan.gsub, plan.gsub_count);
+ apply_table(s, &plan, b, 0);
if (!b->successful || !ensure(b, b->len)) return -1;
position(s, &plan, b);