aboutsummaryrefslogtreecommitdiffstats
path: root/text/shape-build.c
diff options
context:
space:
mode:
Diffstat (limited to 'text/shape-build.c')
-rw-r--r--text/shape-build.c723
1 files changed, 723 insertions, 0 deletions
diff --git a/text/shape-build.c b/text/shape-build.c
new file mode 100644
index 0000000..c5fd237
--- /dev/null
+++ b/text/shape-build.c
@@ -0,0 +1,723 @@
+/* Building shaping sections with HarfBuzz (text/shape-build.h): plans as
+ * HarfBuzz's planner compiles them (hb-ot-shape.cc's features, hb-ot-map.cc's
+ * masks and stages), what the section carries, and a check of it against
+ * HarfBuzz on random runs. */
+#include "shape-build.h"
+#include "text.h"
+#include <hb-ot.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+/* ---- The planner (hb_ot_map_builder_t) */
+
+enum { F_NONE = 0, F_GLOBAL = 1, F_HAS_FALLBACK = 2, F_MANUAL_ZWNJ = 4, F_MANUAL_ZWJ = 8, F_GLOBAL_SEARCH = 16,
+ F_RANDOM = 32, F_PER_SYLLABLE = 64 };
+enum { MAX_VALUE = 255, GLOBAL_BIT_SHIFT = 31, MAX_BITS = 8, MAX_FEATURES = 96, MAX_STAGES = 32 };
+#define GLOBAL_BIT_MASK (1u << GLOBAL_BIT_SHIFT)
+#define NO_FEATURE HB_OT_LAYOUT_NO_FEATURE_INDEX
+#define TAG(s) HB_TAG((s)[0], (s)[1], (s)[2], (s)[3])
+
+typedef struct { hb_tag_t tag; unsigned seq, max_value, flags, default_value, stage[2]; } FeatureInfo;
+typedef struct {
+ hb_tag_t tag;
+ unsigned index[2], stage[2], shift;
+ 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;
+
+typedef struct {
+ hb_face_t *face;
+ unsigned script_index[2], language_index[2];
+ hb_tag_t chosen_script[2];
+ FeatureInfo infos[MAX_FEATURES];
+ unsigned info_count, current_stage[2];
+ bool is_simple;
+ /* compiled */
+ FeatureMap features[MAX_FEATURES];
+ unsigned feature_count;
+ uint32_t global_mask;
+ LookupMap *lookups[2];
+ unsigned lookup_count[2];
+} Map;
+
+static const hb_tag_t table_tags[2] = {HB_OT_TAG_GSUB, HB_OT_TAG_GPOS};
+
+static void add_feature(Map *m, hb_tag_t tag, unsigned flags, unsigned value) {
+ if (!tag || m->info_count == MAX_FEATURES) return;
+ m->infos[m->info_count] = (FeatureInfo){tag, m->info_count, value, flags, flags & F_GLOBAL ? value : 0,
+ {m->current_stage[0], m->current_stage[1]}};
+ 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 bool has_feature(const Map *m, hb_tag_t tag) {
+ for (int t = 0; t < 2; ++t)
+ if (hb_ot_layout_language_find_feature(m->face, table_tags[t], m->script_index[t], m->language_index[t], tag, NULL))
+ return true;
+ return false;
+}
+
+/* The first feature of the table with tag, whatever its script. */
+static bool table_find_feature(hb_face_t *face, hb_tag_t table, hb_tag_t tag, unsigned *index) {
+ hb_tag_t tags[64];
+ unsigned offset = 0, count;
+ do {
+ count = 64;
+ hb_ot_layout_table_get_feature_tags(face, table, offset, &count, tags);
+ for (unsigned i = 0; i < count; ++i)
+ if (tags[i] == tag) {
+ *index = offset + i;
+ return true;
+ }
+ offset += count;
+ } while (count == 64);
+ *index = NO_FEATURE;
+ return false;
+}
+
+static int by_tag_seq(const void *a, const void *b) {
+ const FeatureInfo *x = a, *y = b;
+ return x->tag != y->tag ? (x->tag < y->tag ? -1 : 1) : (x->seq > y->seq) - (x->seq < y->seq);
+}
+static int by_tag(const void *a, const void *b) {
+ const FeatureMap *x = a, *y = b;
+ return (x->tag > y->tag) - (x->tag < y->tag);
+}
+
+static const FeatureMap *find_feature(const Map *m, hb_tag_t tag) {
+ for (unsigned i = 0; i < m->feature_count; ++i)
+ if (m->features[i].tag == tag) return &m->features[i];
+ return NULL;
+}
+static uint32_t one_mask(const Map *m, const char *tag) {
+ const FeatureMap *f = find_feature(m, TAG(tag));
+ return f ? f->one_mask : 0;
+}
+
+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]);
+ unsigned indices[32], offset = 0, len;
+ 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};
+ }
+ offset += len;
+ } while (len == 32);
+ return true;
+}
+
+static bool compile_map(Map *m) {
+ unsigned required_index[2], required_stage[2] = {0, 0};
+ hb_tag_t required_tag[2];
+ for (int t = 0; t < 2; ++t)
+ if (!hb_ot_layout_language_get_required_feature(m->face, table_tags[t], m->script_index[t], m->language_index[t],
+ &required_index[t], &required_tag[t]))
+ required_index[t] = NO_FEATURE, required_tag[t] = 0;
+ /* Sort features and merge duplicates. */
+ if (m->info_count) {
+ if (!m->is_simple) qsort(m->infos, m->info_count, sizeof(*m->infos), by_tag_seq);
+ FeatureInfo *f = m->infos;
+ unsigned j = 0;
+ for (unsigned i = 1; i < m->info_count; ++i)
+ if (f[i].tag != f[j].tag) f[++j] = f[i];
+ else {
+ if (f[i].flags & F_GLOBAL) {
+ f[j].flags |= F_GLOBAL;
+ f[j].max_value = f[i].max_value;
+ f[j].default_value = f[i].default_value;
+ } else {
+ if (f[j].flags & F_GLOBAL) f[j].flags ^= F_GLOBAL;
+ if (f[i].max_value > f[j].max_value) f[j].max_value = f[i].max_value;
+ }
+ f[j].flags |= f[i].flags & F_HAS_FALLBACK;
+ for (int t = 0; t < 2; ++t)
+ if (f[i].stage[t] < f[j].stage[t]) f[j].stage[t] = f[i].stage[t];
+ }
+ m->info_count = j + 1;
+ }
+ /* Allocate bits. */
+ m->global_mask = GLOBAL_BIT_MASK;
+ unsigned next_bit = 3 + 1; /* after the glyph flags HarfBuzz keeps in the mask */
+ for (unsigned i = 0; i < m->info_count; ++i) {
+ const FeatureInfo *info = &m->infos[i];
+ unsigned bits_needed = 0;
+ if (!((info->flags & F_GLOBAL) && info->max_value == 1)) {
+ unsigned storage = 0;
+ for (unsigned v = info->max_value; v; v >>= 1) storage++;
+ bits_needed = storage < MAX_BITS ? storage : MAX_BITS;
+ }
+ if (!info->max_value || next_bit + bits_needed >= GLOBAL_BIT_SHIFT) continue;
+ bool found = false;
+ unsigned index[2];
+ for (int t = 0; t < 2; ++t) {
+ if (required_tag[t] == info->tag) required_stage[t] = info->stage[t];
+ if (hb_ot_layout_language_find_feature(m->face, table_tags[t], m->script_index[t], m->language_index[t],
+ info->tag, &index[t]))
+ found = true;
+ else
+ index[t] = NO_FEATURE;
+ }
+ if (!found && (info->flags & F_GLOBAL_SEARCH))
+ for (int t = 0; t < 2; ++t)
+ found |= table_find_feature(m->face, table_tags[t], info->tag, &index[t]);
+ if (!found && !(info->flags & F_HAS_FALLBACK)) continue;
+ FeatureMap *map = &m->features[m->feature_count++];
+ *map = (FeatureMap){info->tag, {index[0], index[1]}, {info->stage[0], info->stage[1]}, 0, 0, 0,
+ !(info->flags & F_MANUAL_ZWNJ), !(info->flags & F_MANUAL_ZWJ), info->flags & F_RANDOM,
+ info->flags & F_PER_SYLLABLE, !found};
+ if ((info->flags & F_GLOBAL) && info->max_value == 1) {
+ map->shift = GLOBAL_BIT_SHIFT;
+ map->mask = GLOBAL_BIT_MASK;
+ } else {
+ map->shift = next_bit;
+ map->mask = (1u << (next_bit + bits_needed)) - (1u << next_bit);
+ next_bit += bits_needed;
+ m->global_mask |= (info->default_value << map->shift) & map->mask;
+ }
+ 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]++;
+ for (int t = 0; t < 2; ++t) {
+ unsigned allocated = 0, last = 0;
+ for (unsigned stage = 0; stage < m->current_stage[t]; ++stage) {
+ if (required_index[t] != NO_FEATURE && required_stage[t] == stage &&
+ !add_lookups(m, t, required_index[t], GLOBAL_BIT_MASK, true, true, false, false, &allocated))
+ return false;
+ for (unsigned i = 0; i < m->feature_count; ++i) {
+ const FeatureMap *f = &m->features[i];
+ if (f->stage[t] == stage &&
+ !add_lookups(m, t, f->index[t], f->mask, f->auto_zwnj, f->auto_zwj, f->random, f->per_syllable,
+ &allocated))
+ return false;
+ }
+ LookupMap *l = m->lookups[t];
+ unsigned n = m->lookup_count[t];
+ if (last + 1 < n) {
+ for (unsigned i = last + 1; i < n; ++i) /* stable, by index */
+ for (unsigned k = i; k > last && l[k - 1].index > l[k].index; --k) {
+ LookupMap swap = l[k];
+ l[k] = l[k - 1];
+ l[k - 1] = swap;
+ }
+ unsigned j = last;
+ for (unsigned i = j + 1; i < n; ++i)
+ if (l[i].index != l[j].index) l[++j] = l[i];
+ else {
+ l[j].mask |= l[i].mask;
+ l[j].auto_zwnj &= l[i].auto_zwnj;
+ l[j].auto_zwj &= l[i].auto_zwj;
+ }
+ m->lookup_count[t] = j + 1;
+ }
+ last = m->lookup_count[t];
+ }
+ }
+ return true;
+}
+
+/* The scripts HarfBuzz gives the Universal Shaping Engine, or the default
+ * shaper when the font has no script of their own. */
+static const char *const use_scripts[] = {
+ "Tibt", "Mong", "Sinh", "Buhd", "Hano", "Tglg", "Tagb", "Limb", "Tale", "Bugi", "Khar", "Sylo", "Tfng", "Bali",
+ "Nkoo", "Phag", "Cham", "Kali", "Lepc", "Rjng", "Saur", "Sund", "Egyp", "Java", "Kthi", "Mtei", "Lana", "Tavt",
+ "Batk", "Brah", "Mand", "Cakm", "Plrd", "Shrd", "Takr", "Dupl", "Gran", "Khoj", "Sind", "Mahj", "Mani", "Modi",
+ "Hmng", "Phlp", "Sidd", "Tirh", "Ahom", "Mult", "Adlm", "Bhks", "Marc", "Newa", "Gonm", "Soyo", "Zanb", "Dogr",
+ "Gong", "Rohg", "Maka", "Medf", "Sogo", "Sogd", "Elym", "Nand", "Hmnp", "Wcho", "Chrs", "Diak", "Kits", "Yezi",
+ "Cpmn", "Ougr", "Tnsa", "Toto", "Vith", "Kawi", "Nagm", "Gara", "Gukh", "Krai", "Onao", "Sunu", "Todr", "Tutg"};
+static const char *const indic_scripts[] = {"Beng", "Deva", "Gujr", "Guru", "Knda", "Mlym", "Orya", "Taml", "Telu"};
+
+static bool listed(const char *const *list, size_t count, uint32_t script) {
+ for (size_t i = 0; i < count; ++i)
+ if (TAG(list[i]) == script) return true;
+ return false;
+}
+
+/* hb_ot_shaper_categorize, for the shapers the runtime has; -1 for others. */
+static int categorize(uint32_t script, hb_tag_t gsub_script) {
+ bool dflt = gsub_script == HB_OT_TAG_DEFAULT_SCRIPT || gsub_script == TAG("latn");
+ if (script == TAG("Arab")) return VEGA_SHAPER_ARABIC;
+ if (script == TAG("Hebr")) return VEGA_SHAPER_HEBREW;
+ if (script == TAG("Syrc") || script == TAG("Thai") || script == TAG("Laoo") || script == TAG("Hang") ||
+ 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 (script == TAG("Mymr")) return dflt || gsub_script == TAG("mymr") ? VEGA_SHAPER_DEFAULT : -1;
+ return VEGA_SHAPER_DEFAULT;
+}
+
+static bool has_table(hb_face_t *face, hb_tag_t tag) {
+ hb_blob_t *blob = hb_face_reference_table(face, tag);
+ bool has = hb_blob_get_length(blob) > 0;
+ hb_blob_destroy(blob);
+ return has;
+}
+
+typedef struct {
+ uint32_t script;
+ int direction, native, shaper, flags;
+ uint32_t global_mask, masks[VEGA_PLAN_MASKS];
+ LookupMap *lookups[2];
+ unsigned lookup_count[2];
+} Plan;
+
+#define FAIL(...) \
+ do { \
+ snprintf(error, error_size, __VA_ARGS__); \
+ goto fail; \
+ } while (0)
+
+static bool plan_for(hb_face_t *face, uint32_t script_tag, int direction, Plan *plan, char *error, size_t error_size) {
+ Map *m = calloc(1, sizeof(*m));
+ if (!m) {
+ snprintf(error, error_size, "out of memory");
+ return false;
+ }
+ m->face = face;
+ hb_script_t script = hb_script_from_iso15924_tag(script_tag);
+ char name[5] = {(char)(script_tag >> 24), (char)(script_tag >> 16), (char)(script_tag >> 8), (char)script_tag, 0};
+ unsigned script_count = HB_OT_MAX_TAGS_PER_SCRIPT, language_count = HB_OT_MAX_TAGS_PER_LANGUAGE;
+ hb_tag_t script_tags[HB_OT_MAX_TAGS_PER_SCRIPT], language_tags[HB_OT_MAX_TAGS_PER_LANGUAGE];
+ hb_ot_tags_from_script_and_language(script, HB_LANGUAGE_INVALID, &script_count, script_tags, &language_count,
+ language_tags);
+ for (int t = 0; t < 2; ++t) {
+ hb_ot_layout_table_select_script(face, table_tags[t], script_count, script_tags, &m->script_index[t],
+ &m->chosen_script[t]);
+ hb_ot_layout_script_select_language(face, table_tags[t], m->script_index[t], language_count, language_tags,
+ &m->language_index[t]);
+ }
+ int shaper = categorize(script_tag, m->chosen_script[0]);
+ if (shaper < 0) FAIL("%s: HarfBuzz shapes it with a shaper Vega does not have", name);
+ if (has_table(face, HB_TAG('m', 'o', 'r', 'x')) || has_table(face, HB_TAG('k', 'e', 'r', 'x')) ||
+ has_table(face, HB_TAG('t', 'r', 'a', 'k')))
+ FAIL("%s: the font has AAT tables (morx, kerx, trak)", name);
+
+ /* hb_ot_shape_collect_features */
+ m->is_simple = true;
+ enable_feature(m, TAG("rvrn"), F_NONE, 1);
+ add_gsub_pause(m);
+ if (direction == VEGA_SHAPE_LTR) {
+ enable_feature(m, TAG("ltra"), F_NONE, 1);
+ enable_feature(m, TAG("ltrm"), F_NONE, 1);
+ } else {
+ enable_feature(m, TAG("rtla"), F_NONE, 1);
+ add_feature(m, TAG("rtlm"), F_NONE, 1);
+ }
+ add_feature(m, TAG("frac"), F_NONE, 1);
+ add_feature(m, TAG("numr"), F_NONE, 1);
+ add_feature(m, TAG("dnom"), F_NONE, 1);
+ enable_feature(m, TAG("rand"), F_RANDOM, MAX_VALUE);
+ enable_feature(m, TAG("trak"), F_HAS_FALLBACK, 1);
+ enable_feature(m, TAG("Harf"), F_NONE, 1);
+ enable_feature(m, TAG("HARF"), F_NONE, 1);
+ static const char *const arabic_features[7] = {"isol", "fina", "fin2", "fin3", "medi", "med2", "init"};
+ if (shaper == VEGA_SHAPER_ARABIC) {
+ m->is_simple = false;
+ enable_feature(m, TAG("stch"), F_NONE, 1);
+ add_gsub_pause(m); /* record_stch */
+ enable_feature(m, TAG("ccmp"), F_MANUAL_ZWJ, 1);
+ enable_feature(m, TAG("locl"), F_MANUAL_ZWJ, 1);
+ add_gsub_pause(m);
+ 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);
+ }
+ enable_feature(m, TAG("rlig"), F_MANUAL_ZWJ | F_HAS_FALLBACK, 1);
+ if (script == HB_SCRIPT_ARABIC) add_gsub_pause(m); /* arabic_fallback_shape */
+ enable_feature(m, TAG("calt"), F_MANUAL_ZWJ, 1);
+ if (!has_feature(m, TAG("rclt"))) add_gsub_pause(m);
+ 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);
+ }
+ enable_feature(m, TAG("Buzz"), F_NONE, 1);
+ enable_feature(m, TAG("BUZZ"), F_NONE, 1);
+ static const char *const common[] = {"abvm", "blwm", "ccmp", "locl", "mark", "mkmk", "rlig"};
+ for (int i = 0; i < 7; ++i)
+ 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);
+ if (!compile_map(m)) FAIL("out of memory");
+
+ /* hb_ot_shape_planner_t::compile */
+ const FeatureMap *kern = find_feature(m, TAG("kern"));
+ bool has_gpos_kern = kern && kern->index[1] != NO_FEATURE;
+ bool disable_gpos = shaper == VEGA_SHAPER_HEBREW && m->chosen_script[1] != TAG("hebr");
+ bool apply_gpos = !disable_gpos && hb_ot_layout_has_positioning(face);
+ if ((!has_gpos_kern || !apply_gpos) && has_table(face, HB_TAG('k', 'e', 'r', 'n')))
+ FAIL("%s: HarfBuzz would kern with the font's kern table", name);
+ if (!apply_gpos) FAIL("%s: HarfBuzz would position without GPOS (fallback positioning)", name);
+ 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);
+ }
+ 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");
+ *plan = (Plan){script_tag, direction, native == HB_DIRECTION_LTR ? VEGA_SHAPE_LTR : native == HB_DIRECTION_RTL ? VEGA_SHAPE_RTL : 0,
+ shaper,
+ VEGA_PLAN_GPOS | VEGA_PLAN_ZERO_MARKS | (hb_ot_layout_has_glyph_classes(face) ? 0 : VEGA_PLAN_GUESS_CLASSES) |
+ (one_mask(m, "mark") ? VEGA_PLAN_GPOS_MARK : 0) | (frac || (numr && dnom) ? VEGA_PLAN_FRACTIONS : 0),
+ m->global_mask, {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]);
+ 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;
+ free(m);
+ return true;
+fail:
+ free(m->lookups[0]);
+ free(m->lookups[1]);
+ free(m);
+ return false;
+}
+
+/* ---- The section */
+
+typedef struct { unsigned char *bytes; size_t size, allocated; bool failed; } Out;
+
+static void put(Out *o, const void *bytes, size_t n) {
+ if (o->failed) return;
+ if (o->size + n > o->allocated) {
+ size_t a = o->allocated ? o->allocated : 4096;
+ while (a < o->size + n) a *= 2;
+ unsigned char *grown = realloc(o->bytes, a);
+ if (!grown) {
+ o->failed = true;
+ return;
+ }
+ o->bytes = grown;
+ o->allocated = a;
+ }
+ memcpy(o->bytes + o->size, bytes, n);
+ o->size += n;
+}
+static void put32(Out *o, uint32_t v) {
+ unsigned char b[4] = {(unsigned char)v, (unsigned char)(v >> 8), (unsigned char)(v >> 16), (unsigned char)(v >> 24)};
+ put(o, b, 4);
+}
+static void put16(Out *o, uint32_t v) {
+ unsigned char b[2] = {(unsigned char)v, (unsigned char)(v >> 8)};
+ put(o, b, 2);
+}
+static void align4(Out *o) {
+ static const unsigned char zero[4] = {0};
+ put(o, zero, (4 - o->size % 4) % 4);
+}
+static void set32(Out *o, size_t at, uint32_t v) {
+ if (o->failed) return;
+ for (int i = 0; i < 4; ++i) o->bytes[at + i] = (unsigned char)(v >> (8 * i));
+}
+
+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));
+ size_t plan_count = 0;
+ Out o = {0};
+ hb_font_t *font = hb_font_create(face);
+ hb_set_t *unicodes = hb_set_create();
+ if (!plans) FAIL("out of memory");
+ /* Both directions: a run's comes from bidi, and need not be its script's. */
+ for (size_t i = 0; i < script_count; ++i)
+ for (int d = VEGA_SHAPE_LTR; d <= VEGA_SHAPE_RTL; ++d) {
+ if (!plan_for(face, scripts[i], d, &plans[plan_count], error, error_size)) goto fail;
+ plan_count++;
+ }
+ unsigned glyph_count = hb_face_get_glyph_count(face);
+ if (glyph_count > 65535) FAIL("more than 65535 glyphs");
+ hb_face_collect_unicodes(face, unicodes);
+ uint32_t cmap_count = 0;
+ for (hb_codepoint_t u = HB_SET_VALUE_INVALID; hb_set_next(unicodes, &u);) {
+ hb_codepoint_t g;
+ if (hb_font_get_nominal_glyph(font, u, &g) && g) cmap_count++;
+ }
+ hb_blob_t *tables[3] = {hb_face_reference_table(face, HB_OT_TAG_GDEF), hb_face_reference_table(face, HB_OT_TAG_GSUB),
+ hb_face_reference_table(face, HB_OT_TAG_GPOS)};
+ /* The header, filled in as the parts land. */
+ put(&o, "VSHP", 4);
+ put32(&o, 0);
+ put16(&o, hb_face_get_upem(face));
+ put16(&o, glyph_count);
+ for (int i = 0; i < 13; ++i) put32(&o, 0);
+ set32(&o, 12, cmap_count);
+ set32(&o, 16, (uint32_t)o.size);
+ for (hb_codepoint_t u = HB_SET_VALUE_INVALID; hb_set_next(unicodes, &u);) {
+ hb_codepoint_t g;
+ if (hb_font_get_nominal_glyph(font, u, &g) && g) {
+ put32(&o, u);
+ put32(&o, g);
+ }
+ }
+ set32(&o, 20, (uint32_t)o.size);
+ for (unsigned g = 0; g < glyph_count; ++g) {
+ hb_position_t advance = hb_font_get_glyph_h_advance(font, g);
+ if (advance < 0 || advance > 65535) {
+ for (int k = 0; k < 3; ++k) hb_blob_destroy(tables[k]);
+ FAIL("glyph %u: an advance outside 0 to 65535", g);
+ }
+ put16(&o, (uint32_t)advance);
+ }
+ for (int k = 0; k < 3; ++k) {
+ align4(&o);
+ unsigned length;
+ const char *data = hb_blob_get_data(tables[k], &length);
+ set32(&o, 24 + 8 * k, length ? (uint32_t)o.size : 0);
+ set32(&o, 28 + 8 * k, length);
+ put(&o, data, length);
+ hb_blob_destroy(tables[k]);
+ }
+ align4(&o);
+ set32(&o, 48, (uint32_t)plan_count);
+ set32(&o, 52, (uint32_t)o.size);
+ uint32_t step = 0;
+ for (size_t i = 0; i < plan_count; ++i) {
+ const Plan *p = &plans[i];
+ put32(&o, p->script);
+ unsigned char small[4] = {(unsigned char)p->direction, (unsigned char)p->native, (unsigned char)p->shaper,
+ (unsigned char)p->flags};
+ put(&o, small, 4);
+ put32(&o, p->global_mask);
+ for (int k = 0; k < VEGA_PLAN_MASKS; ++k) put32(&o, p->masks[k]);
+ for (int t = 0; t < 2; ++t) {
+ put32(&o, step);
+ put32(&o, p->lookup_count[t]);
+ step += p->lookup_count[t];
+ }
+ }
+ set32(&o, 56, step);
+ set32(&o, 60, (uint32_t)o.size);
+ for (size_t i = 0; i < plan_count; ++i)
+ for (int t = 0; t < 2; ++t)
+ 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)),
+ 0};
+ put(&o, flags, 2);
+ put32(&o, l->mask);
+ }
+ set32(&o, 4, (uint32_t)o.size);
+ if (o.failed) FAIL("out of memory");
+
+ if (glyphs) {
+ /* What normalization can reach from codes: them, what they
+ * decompose into and compose to, mirrors, the space; then what GSUB
+ * can make of those. */
+ hb_set_t *wanted = hb_set_create(), *lookups = hb_set_create();
+ for (size_t i = 0; i < code_count; ++i) {
+ hb_set_add(wanted, codes[i]);
+ uint32_t mirror = vega_mirror(codes[i]);
+ if (mirror) hb_set_add(wanted, mirror);
+ const uint32_t *parts;
+ size_t n = vega_decomposition(codes[i], &parts);
+ for (size_t k = 0; k < n; ++k) hb_set_add(wanted, parts[k]);
+ }
+ static const uint32_t always[] = {0x0020, 0x2010, 0x25CC};
+ for (size_t i = 0; i < sizeof(always) / sizeof(*always); ++i) hb_set_add(wanted, always[i]);
+ for (hb_codepoint_t u = HB_SET_VALUE_INVALID; hb_set_next(unicodes, &u);) {
+ const uint32_t *parts;
+ size_t n = vega_decomposition(u, &parts);
+ bool composed = n > 0;
+ for (size_t k = 0; k < n && composed; ++k) composed = hb_set_has(wanted, parts[k]);
+ if (composed) hb_set_add(wanted, u);
+ }
+ hb_set_add(glyphs, 0);
+ for (hb_codepoint_t u = HB_SET_VALUE_INVALID; hb_set_next(wanted, &u);) {
+ hb_codepoint_t g;
+ 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);
+ hb_ot_layout_lookups_substitute_closure(face, lookups, glyphs);
+ hb_set_destroy(wanted);
+ hb_set_destroy(lookups);
+ }
+ for (size_t i = 0; i < plan_count; ++i) free(plans[i].lookups[0]), free(plans[i].lookups[1]);
+ free(plans);
+ hb_set_destroy(unicodes);
+ hb_font_destroy(font);
+ *out = (VegaSection){o.bytes, o.size};
+ return true;
+fail:
+ if (plans)
+ for (size_t i = 0; i < plan_count; ++i) free(plans[i].lookups[0]), free(plans[i].lookups[1]);
+ free(plans);
+ free(o.bytes);
+ hb_set_destroy(unicodes);
+ hb_font_destroy(font);
+ return false;
+}
+
+/* ---- The check */
+
+static double now(void) {
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ return (double)t.tv_sec + (double)t.tv_nsec / 1e9;
+}
+
+typedef struct { uint64_t s; } Random;
+static uint64_t next_random(Random *r) {
+ r->s ^= r->s >> 12;
+ r->s ^= r->s << 25;
+ r->s ^= r->s >> 27;
+ return r->s * 0x2545F4914F6CDD1DULL;
+}
+static uint32_t below(Random *r, uint32_t n) { return n ? (uint32_t)(next_random(r) % n) : 0; }
+
+typedef struct { uint32_t *items; size_t count; } Pool;
+static void pool_add(Pool *p, uint32_t code) {
+ if (!(p->count & (p->count - 1)) || !p->count) {
+ uint32_t *grown = realloc(p->items, (p->count ? 2 * p->count : 16) * sizeof(*grown));
+ if (!grown) return;
+ p->items = grown;
+ }
+ p->items[p->count++] = code;
+}
+static uint32_t pick(Random *r, const Pool *p) { return p->items[below(r, (uint32_t)p->count)]; }
+
+static void describe(FILE *report, const char *who, const VegaShaped *g, unsigned count) {
+ fprintf(report, " %-8s", who);
+ for (unsigned i = 0; i < count; ++i)
+ fprintf(report, " %u@%u(%d,%d+%d,%d)", g[i].glyph, g[i].cluster, g[i].x_advance, g[i].y_advance, g[i].x_offset,
+ g[i].y_offset);
+ fprintf(report, "\n");
+}
+
+void vega_shaping_check(hb_font_t *font, const VegaShaping *s, uint32_t script, int direction, const uint32_t *codes,
+ size_t code_count, uint64_t seed, size_t runs, VegaShapeCheck *out, FILE *report) {
+ *out = (VegaShapeCheck){0};
+ /* 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};
+ 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)
+ hb_ot_layout_lookup_collect_glyphs(hb_font_get_face(font), HB_OT_TAG_GSUB, t, NULL, inputs, NULL, NULL);
+ for (size_t i = 0; i < code_count; ++i) {
+ uint32_t code = codes[i], of = vega_script_code(vega_script(code));
+ VegaCategory c = vega_category(code);
+ hb_codepoint_t glyph;
+ if (!hb_font_get_nominal_glyph(font, code, &glyph) || c == VEGA_CC || c == VEGA_CS || c == VEGA_CO) continue;
+ bool mark = c == VEGA_MN || c == VEGA_MC || c == VEGA_ME;
+ 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);
+ else continue;
+ if (hb_set_has(inputs, glyph)) pool_add(&hot, code);
+ }
+ hb_set_destroy(inputs);
+ if (!letters.count) letters = others, others = (Pool){0};
+ VegaShaper *shaper = vega_shaper_new();
+ hb_buffer_t *buffer = hb_buffer_create();
+ Random random = {seed * 0x9E3779B97F4A7C15ULL + script + 1};
+ uint32_t text[256];
+ bool rtl_script = direction == VEGA_SHAPE_RTL;
+ unsigned mark_rate = rtl_script ? 40 : 12;
+ for (size_t run = 0; run < runs && letters.count && shaper; ++run) {
+ size_t n = 0;
+ if (below(&random, 4)) {
+ /* Words: letters (often ones substitution starts from), marks after
+ * some, spaces and punctuation between. */
+ unsigned words = 1 + below(&random, 6);
+ for (unsigned w = 0; w < words && n < 200; ++w) {
+ if (w) {
+ if (others.count && !below(&random, 6)) text[n++] = pick(&random, &others);
+ text[n++] = ' ';
+ }
+ unsigned length = 1 + below(&random, 8);
+ for (unsigned k = 0; k < length && n < 200; ++k) {
+ 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);
+ if (!below(&random, 5)) text[n++] = pick(&random, &marks);
+ if (!below(&random, 20)) text[n++] = pick(&random, &marks);
+ }
+ if (!below(&random, 40)) text[n++] = below(&random, 2) ? 0x200C : 0x200D;
+ }
+ }
+ } else {
+ /* Anything, in any order: marks first, marks in piles, joiners. */
+ unsigned length = 1 + below(&random, 24);
+ for (unsigned k = 0; k < length; ++k) {
+ unsigned kind = below(&random, 10);
+ const Pool *p = kind < 4 ? &letters : kind < 7 && marks.count ? &marks : kind < 8 && hot.count ? &hot : &others;
+ text[n++] = kind == 9 ? (below(&random, 3) ? ' ' : below(&random, 2) ? 0x200C : 0x200D)
+ : p->count ? pick(&random, p) : pick(&random, &letters);
+ }
+ }
+ const VegaShaped *ours;
+ double start = now();
+ long count = vega_shape(shaper, s, text, n, script, direction, &ours);
+ double middle = now();
+ hb_buffer_clear_contents(buffer);
+ hb_buffer_add_codepoints(buffer, text, (int)n, 0, (int)n);
+ hb_buffer_set_direction(buffer, direction == VEGA_SHAPE_RTL ? HB_DIRECTION_RTL : HB_DIRECTION_LTR);
+ hb_buffer_set_script(buffer, hb_script_from_iso15924_tag(script));
+ hb_shape(font, buffer, NULL, 0);
+ out->vega_seconds += middle - start;
+ out->harfbuzz_seconds += now() - middle;
+ unsigned theirs_count;
+ hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, &theirs_count);
+ hb_glyph_position_t *pos = hb_buffer_get_glyph_positions(buffer, &theirs_count);
+ bool same = count == (long)theirs_count;
+ for (unsigned i = 0; same && i < theirs_count; ++i)
+ same = ours[i].glyph == info[i].codepoint && ours[i].cluster == info[i].cluster &&
+ ours[i].x_advance == pos[i].x_advance && ours[i].y_advance == pos[i].y_advance &&
+ ours[i].x_offset == pos[i].x_offset && ours[i].y_offset == pos[i].y_offset;
+ out->runs++;
+ out->characters += n;
+ out->glyphs += theirs_count;
+ if (same) continue;
+ if (report && out->differences < 5) {
+ fprintf(report, "%c%c%c%c run %zu:", (char)(script >> 24), (char)(script >> 16), (char)(script >> 8),
+ (char)script, run);
+ for (size_t i = 0; i < n; ++i) fprintf(report, " %04X", text[i]);
+ fprintf(report, "\n");
+ if (count >= 0) describe(report, "vega", ours, (unsigned)count);
+ else fprintf(report, " vega (failed)\n");
+ VegaShaped *theirs = malloc((theirs_count + 1) * sizeof(*theirs));
+ for (unsigned i = 0; theirs && i < theirs_count; ++i)
+ theirs[i] = (VegaShaped){info[i].codepoint, info[i].cluster, pos[i].x_advance, pos[i].y_advance,
+ pos[i].x_offset, pos[i].y_offset};
+ if (theirs) describe(report, "harfbuzz", theirs, theirs_count);
+ free(theirs);
+ }
+ out->differences++;
+ }
+ hb_buffer_destroy(buffer);
+ vega_shaper_free(shaper);
+ free(letters.items);
+ free(marks.items);
+ free(others.items);
+ free(hot.items);
+}