summaryrefslogtreecommitdiff
path: root/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl')
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/build.c320
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/classdef.c237
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/constants.c24
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/coverage.c256
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/dump.c87
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/otl.c104
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/parse.c376
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/private.h24
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/read.c267
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining.h28
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/build.c239
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/classifier.c238
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/common.c23
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/common.h33
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/dump.c27
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/parse.c37
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/read.c416
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/common.h15
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/extend.c35
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/extend.h13
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-common.c214
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-common.h43
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-cursive.c101
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-cursive.h13
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-ligature.c241
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-ligature.h14
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-single.c203
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-single.h14
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-pair.c406
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-pair.h13
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-single.c129
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-single.h13
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-ligature.c177
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-ligature.h13
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-multi.c91
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-multi.h13
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-reverse.c133
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-reverse.h13
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-single.c139
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-single.h13
40 files changed, 4795 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/build.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/build.c
new file mode 100644
index 00000000000..91739f11589
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/build.c
@@ -0,0 +1,320 @@
+#include "private.h"
+
+#define LARGE_SUBTABLE_LIMIT 4096
+
+static uint32_t featureNameToTag(const sds name) {
+ uint32_t tag = 0;
+ if (sdslen(name) > 0) {
+ tag |= ((uint8_t)name[0]) << 24;
+ } else {
+ tag |= ((uint8_t)' ') << 24;
+ }
+ if (sdslen(name) > 1) {
+ tag |= ((uint8_t)name[1]) << 16;
+ } else {
+ tag |= ((uint8_t)' ') << 16;
+ }
+ if (sdslen(name) > 2) {
+ tag |= ((uint8_t)name[2]) << 8;
+ } else {
+ tag |= ((uint8_t)' ') << 8;
+ }
+ if (sdslen(name) > 3) {
+ tag |= ((uint8_t)name[3]) << 0;
+ } else {
+ tag |= ((uint8_t)' ') << 0;
+ }
+ return tag;
+}
+
+typedef caryll_Buffer *(*_otl_Builder)(const otl_Subtable *_subtable,
+ otl_BuildHeuristics heuristics);
+
+static tableid_t _declare_lookup_writer(otl_LookupType type, _otl_Builder fn,
+ const otl_Lookup *lookup, caryll_Buffer ***subtables,
+ size_t *lastOffset, bool *preferExtensionForThisLUT,
+ otl_BuildHeuristics heuristics) {
+ if (lookup->type == type) {
+ NEW(*subtables, lookup->subtables.length);
+ size_t totalBufSizeShort = 0;
+ size_t totalBufSizeExt = 0;
+ for (tableid_t j = 0; j < lookup->subtables.length; j++) {
+ caryll_Buffer *buf = fn(lookup->subtables.items[j], heuristics);
+ (*subtables)[j] = buf;
+ totalBufSizeShort += buf->size;
+ totalBufSizeExt += 8;
+ }
+ if (totalBufSizeShort > LARGE_SUBTABLE_LIMIT) {
+ *lastOffset += totalBufSizeExt;
+ *preferExtensionForThisLUT = true;
+ } else {
+ *lastOffset += totalBufSizeShort;
+ *preferExtensionForThisLUT = false;
+ }
+ return lookup->subtables.length;
+ }
+ return 0;
+}
+
+#define LOOKUP_WRITER(type, fn) \
+ if (!written) \
+ written = _declare_lookup_writer(type, fn, lookup, subtables, lastOffset, \
+ preferExtensionForThisLUT, heuristics);
+
+static tableid_t _build_lookup(const otl_Lookup *lookup, caryll_Buffer ***subtables,
+ size_t *lastOffset, bool *preferExtensionForThisLUT,
+ otl_BuildHeuristics heuristics) {
+ if (lookup->type == otl_type_gpos_chaining || lookup->type == otl_type_gsub_chaining) {
+ return otfcc_classifiedBuildChaining(lookup, subtables, lastOffset);
+ }
+
+ tableid_t written = 0;
+ LOOKUP_WRITER(otl_type_gsub_single, otfcc_build_gsub_single_subtable);
+ LOOKUP_WRITER(otl_type_gsub_multiple, otfcc_build_gsub_multi_subtable);
+ LOOKUP_WRITER(otl_type_gsub_alternate, otfcc_build_gsub_multi_subtable);
+ LOOKUP_WRITER(otl_type_gsub_ligature, otfcc_build_gsub_ligature_subtable);
+ LOOKUP_WRITER(otl_type_gsub_reverse, otfcc_build_gsub_reverse);
+ LOOKUP_WRITER(otl_type_gpos_single, otfcc_build_gpos_single);
+ LOOKUP_WRITER(otl_type_gpos_pair, otfcc_build_gpos_pair);
+ LOOKUP_WRITER(otl_type_gpos_cursive, otfcc_build_gpos_cursive);
+ LOOKUP_WRITER(otl_type_gpos_markToBase, otfcc_build_gpos_markToSingle);
+ LOOKUP_WRITER(otl_type_gpos_markToMark, otfcc_build_gpos_markToSingle);
+ LOOKUP_WRITER(otl_type_gpos_markToLigature, otfcc_build_gpos_markToLigature);
+ return written;
+}
+
+static otl_BuildHeuristics getLookupHeuristics(const table_OTL *table, const otl_Lookup *lut) {
+ otl_BuildHeuristics heu = OTL_BH_NORMAL;
+ // GSUB VERT heuristics
+ // GDI have some restrictions on the internal format of the lookup inisde a VERT feature
+ if (lut->type == otl_type_gsub_single) {
+ for (tableid_t j = 0; j < table->features.length; j++) {
+ const otl_Feature *fea = table->features.items[j];
+ if (featureNameToTag(fea->name) != OTFCC_CHR('v','e','r','t')) continue;
+ for (tableid_t k = 0; k < fea->lookups.length; k++) {
+ if (fea->lookups.items[k] == lut) heu |= OTL_BH_GSUB_VERT;
+ }
+ }
+ }
+ return heu;
+}
+
+// When writing lookups, otfcc will try to maintain everything correctly.
+// That is, we will use extended layout lookups automatically when the
+// offsets are too large.
+static bk_Block *writeOTLLookups(const table_OTL *table, const otfcc_Options *options,
+ const char *tag) {
+ caryll_Buffer ***subtables;
+ NEW(subtables, table->lookups.length);
+ bool *preferExtForThisLut;
+ tableid_t *subtableQuantity;
+ NEW(subtableQuantity, table->lookups.length);
+ NEW(preferExtForThisLut, table->lookups.length);
+
+ size_t lastOffset = 0;
+ for (tableid_t j = 0; j < table->lookups.length; j++) {
+ otl_Lookup *lookup = table->lookups.items[j];
+ otl_BuildHeuristics heu = getLookupHeuristics(table, lookup);
+ logProgress("Building lookup %s (%u/%u)\n", lookup->name, j,
+ (uint32_t)table->lookups.length);
+ subtableQuantity[j] =
+ _build_lookup(lookup, &(subtables[j]), &lastOffset, &(preferExtForThisLut[j]), heu);
+ }
+
+ size_t headerSize = 2 + 2 * table->lookups.length;
+ for (tableid_t j = 0; j < table->lookups.length; j++) {
+ if (subtableQuantity[j]) { headerSize += 6 + 2 * subtableQuantity[j]; }
+ }
+ bool useExtended = lastOffset >= 0xFF00 - headerSize;
+
+ bk_Block *root = bk_new_Block(b16, table->lookups.length, // LookupCount
+ bkover);
+ for (tableid_t j = 0; j < table->lookups.length; j++) {
+ if (!subtableQuantity[j]) {
+ logNotice("Lookup %s is empty.\n", table->lookups.items[j]->name);
+ }
+ otl_Lookup *lookup = table->lookups.items[j];
+ const bool canBeContextual = otfcc_chainingLookupIsContextualLookup(lookup);
+ const bool useExtendedForIt = useExtended || preferExtForThisLut[j];
+ if (useExtendedForIt) {
+ logNotice("[OTFCC-fea] Using extended OpenType table layout for %s/%s.\n", tag,
+ lookup->name);
+ }
+ uint16_t lookupType =
+ useExtendedForIt
+ ? (lookup->type > otl_type_gpos_unknown
+ ? otl_type_gpos_extend - otl_type_gpos_unknown
+ : lookup->type > otl_type_gsub_unknown
+ ? otl_type_gsub_extend - otl_type_gsub_unknown
+ : 0)
+ : (lookup->type > otl_type_gpos_unknown
+ ? lookup->type - otl_type_gpos_unknown
+ : lookup->type > otl_type_gsub_unknown ? lookup->type - otl_type_gsub_unknown
+ : 0) -
+ (canBeContextual ? 1 : 0);
+
+ bk_Block *blk = bk_new_Block(b16, lookupType, // LookupType
+ b16, lookup->flags, // LookupFlag
+ b16, subtableQuantity[j], // SubTableCount
+ bkover);
+
+ for (tableid_t k = 0; k < subtableQuantity[j]; k++) {
+ if (useExtendedForIt) {
+ uint16_t extensionLookupType = (lookup->type > otl_type_gpos_unknown
+ ? lookup->type - otl_type_gpos_unknown
+ : lookup->type > otl_type_gsub_unknown
+ ? lookup->type - otl_type_gsub_unknown
+ : 0) -
+ (canBeContextual ? 1 : 0);
+
+ bk_Block *stub =
+ bk_new_Block(b16, 1, // format
+ b16, extensionLookupType, // ExtensionLookupType
+ p32, bk_newBlockFromBuffer(subtables[j][k]), // ExtensionOffset
+ bkover);
+ bk_push(blk, p16, stub, bkover);
+ } else {
+ bk_push(blk, p16, bk_newBlockFromBuffer(subtables[j][k]), bkover);
+ }
+ }
+ bk_push(blk, b16, 0, // MarkFilteringSet
+ bkover);
+ bk_push(root, p16, blk, bkover);
+ FREE(subtables[j]);
+ }
+ FREE(subtables);
+ FREE(subtableQuantity);
+ FREE(preferExtForThisLut);
+ return root;
+}
+
+static bk_Block *writeOTLFeatures(const table_OTL *table, const otfcc_Options *options) {
+ bk_Block *root = bk_new_Block(b16, table->features.length, bkover);
+ for (tableid_t j = 0; j < table->features.length; j++) {
+ bk_Block *fea = bk_new_Block(p16, NULL, // FeatureParams
+ b16, table->features.items[j]->lookups.length, // LookupCount
+ bkover);
+ for (tableid_t k = 0; k < table->features.items[j]->lookups.length; k++) {
+ // reverse lookup
+ for (tableid_t l = 0; l < table->lookups.length; l++) {
+ if (table->features.items[j]->lookups.items[k] == table->lookups.items[l]) {
+ bk_push(fea, b16, l, bkover);
+ break;
+ }
+ }
+ }
+ bk_push(root, b32, featureNameToTag(table->features.items[j]->name), // FeatureTag
+ p16, fea, // Feature
+ bkover);
+ }
+ return root;
+}
+
+// script hash
+typedef struct {
+ sds tag;
+ uint16_t lc;
+ otl_LanguageSystem *dl;
+ otl_LanguageSystem **ll;
+ UT_hash_handle hh;
+} script_stat_hash;
+
+static tableid_t featureIndex(const otl_Feature *feature, const table_OTL *table) {
+ for (tableid_t j = 0; j < table->features.length; j++)
+ if (table->features.items[j] == feature) { return j; }
+ return 0xFFFF;
+}
+static bk_Block *writeLanguage(otl_LanguageSystem *lang, const table_OTL *table) {
+ if (!lang) return NULL;
+ bk_Block *root =
+ bk_new_Block(p16, NULL, // LookupOrder
+ b16, featureIndex(lang->requiredFeature, table), // ReqFeatureIndex
+ b16, lang->features.length, // FeatureCount
+ bkover);
+ for (tableid_t k = 0; k < lang->features.length; k++) {
+ bk_push(root, b16, featureIndex(lang->features.items[k], table), bkover);
+ }
+ return root;
+}
+
+static bk_Block *writeScript(script_stat_hash *script, const table_OTL *table) {
+ bk_Block *root = bk_new_Block(p16, writeLanguage(script->dl, table), // DefaultLangSys
+ b16, script->lc, // LangSysCount
+ bkover);
+
+ for (tableid_t j = 0; j < script->lc; j++) {
+ sds tag = sdsnewlen(script->ll[j]->name + 5, 4);
+
+ bk_push(root, b32, featureNameToTag(tag), // LangSysTag
+ p16, writeLanguage(script->ll[j], table), // LangSys
+ bkover);
+ sdsfree(tag);
+ }
+ return root;
+}
+static bk_Block *writeOTLScriptAndLanguages(const table_OTL *table, const otfcc_Options *options) {
+ script_stat_hash *h = NULL;
+ for (tableid_t j = 0; j < table->languages.length; j++) {
+ otl_LanguageSystem *language = table->languages.items[j];
+ sds scriptTag = sdsnewlen(language->name, 4);
+ bool isDefault = strncmp(language->name + 5, "DFLT", 4) == 0 ||
+ strncmp(language->name + 5, "dflt", 4) == 0;
+
+ script_stat_hash *s = NULL;
+ HASH_FIND_STR(h, scriptTag, s);
+ if (s) {
+ if (isDefault) {
+ s->dl = language;
+ } else {
+ s->lc += 1;
+ s->ll[s->lc - 1] = language;
+ }
+ sdsfree(scriptTag);
+ } else {
+ NEW(s);
+ s->tag = scriptTag;
+ s->dl = NULL;
+ NEW(s->ll, table->languages.length);
+ if (isDefault) {
+ s->dl = language;
+ s->lc = 0;
+ } else {
+ s->lc = 1;
+ s->ll[s->lc - 1] = language;
+ }
+ HASH_ADD_STR(h, tag, s);
+ }
+ }
+
+ bk_Block *root = bk_new_Block(b16, HASH_COUNT(h), bkover);
+
+ script_stat_hash *s, *tmp;
+ HASH_ITER(hh, h, s, tmp) {
+ bk_push(root, b32, featureNameToTag(s->tag), // ScriptTag
+ p16, writeScript(s, table), // Script
+ bkover);
+ HASH_DEL(h, s);
+ sdsfree(s->tag);
+ FREE(s->ll);
+ FREE(s);
+ }
+ return root;
+}
+
+caryll_Buffer *otfcc_buildOtl(const table_OTL *table, const otfcc_Options *options,
+ const char *tag) {
+ if (!table) return NULL;
+ caryll_Buffer *buf;
+ loggedStep("%s", tag) {
+ bk_Block *lookups = writeOTLLookups(table, options, tag);
+ bk_Block *features = writeOTLFeatures(table, options);
+ bk_Block *languages = writeOTLScriptAndLanguages(table, options);
+ bk_Block *root = bk_new_Block(b32, 0x10000, // Version
+ p16, languages, // ScriptList
+ p16, features, // FeatureList
+ p16, lookups, // LookupList
+ bkover);
+ buf = bk_build_Block(root);
+ }
+ return buf;
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/classdef.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/classdef.c
new file mode 100644
index 00000000000..da09b2b3bee
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/classdef.c
@@ -0,0 +1,237 @@
+#include "support/util.h"
+#include "otfcc/table/otl/classdef.h"
+
+static INLINE void disposeClassDef(otl_ClassDef *cd) {
+ if (cd->glyphs) {
+ for (glyphid_t j = 0; j < cd->numGlyphs; j++) {
+ Handle.dispose(&cd->glyphs[j]);
+ }
+ FREE(cd->glyphs);
+ }
+ FREE(cd->classes);
+}
+caryll_standardRefTypeFn(otl_ClassDef, disposeClassDef);
+
+static void growClassdef(otl_ClassDef *cd, uint32_t n) {
+ if (!n) return;
+ if (n > cd->capacity) {
+ if (!cd->capacity) cd->capacity = 0x10;
+ while (n > cd->capacity)
+ cd->capacity += (cd->capacity >> 1) & 0xFFFFFF;
+ RESIZE(cd->glyphs, cd->capacity);
+ RESIZE(cd->classes, cd->capacity);
+ }
+}
+static void pushClassDef(otl_ClassDef *cd, MOVE otfcc_GlyphHandle h, glyphclass_t cls) {
+ cd->numGlyphs += 1;
+ growClassdef(cd, cd->numGlyphs);
+ cd->glyphs[cd->numGlyphs - 1] = h;
+ cd->classes[cd->numGlyphs - 1] = cls;
+ if (cls > cd->maxclass) cd->maxclass = cls;
+}
+
+typedef struct {
+ int gid;
+ int covIndex;
+ UT_hash_handle hh;
+} coverage_entry;
+
+static int by_covIndex(coverage_entry *a, coverage_entry *b) {
+ return a->covIndex - b->covIndex;
+}
+
+static otl_ClassDef *readClassDef(const uint8_t *data, uint32_t tableLength, uint32_t offset) {
+ otl_ClassDef *cd = otl_iClassDef.create();
+ if (tableLength < offset + 4) return cd;
+ uint16_t format = read_16u(data + offset);
+ if (format == 1 && tableLength >= offset + 6) {
+ glyphid_t startGID = read_16u(data + offset + 2);
+ glyphid_t count = read_16u(data + offset + 4);
+ if (count && tableLength >= offset + 6 + count * 2) {
+ for (glyphid_t j = 0; j < count; j++) {
+ pushClassDef(cd, Handle.fromIndex(startGID + j), read_16u(data + offset + 6 + j * 2));
+ }
+ return cd;
+ }
+ } else if (format == 2) {
+ // The ranges may overlap.
+ // Use hashtable.
+ uint16_t rangeCount = read_16u(data + offset + 2);
+ if (tableLength < offset + 4 + rangeCount * 6) return cd;
+ coverage_entry *hash = NULL;
+ for (uint16_t j = 0; j < rangeCount; j++) {
+ uint16_t start = read_16u(data + offset + 4 + 6 * j);
+ uint16_t end = read_16u(data + offset + 4 + 6 * j + 2);
+ uint16_t cls = read_16u(data + offset + 4 + 6 * j + 4);
+ for (int k = start; k <= end; k++) {
+ coverage_entry *item = NULL;
+ HASH_FIND_INT(hash, &k, item);
+ if (!item) {
+ NEW(item);
+ item->gid = k;
+ item->covIndex = cls;
+ HASH_ADD_INT(hash, gid, item);
+ }
+ }
+ }
+ HASH_SORT(hash, by_covIndex);
+ coverage_entry *e, *tmp;
+ HASH_ITER(hh, hash, e, tmp) {
+ pushClassDef(cd, Handle.fromIndex(e->gid), e->covIndex);
+ HASH_DEL(hash, e);
+ FREE(e);
+ }
+ return cd;
+ }
+ return cd;
+}
+
+static otl_ClassDef *expandClassDef(otl_Coverage *cov, otl_ClassDef *ocd) {
+ otl_ClassDef *cd = otl_iClassDef.create();
+ coverage_entry *hash = NULL;
+ for (glyphid_t j = 0; j < ocd->numGlyphs; j++) {
+ int gid = ocd->glyphs[j].index;
+ int cid = ocd->classes[j];
+ coverage_entry *item = NULL;
+ HASH_FIND_INT(hash, &gid, item);
+ if (!item) {
+ NEW(item);
+ item->gid = gid;
+ item->covIndex = cid;
+ HASH_ADD_INT(hash, gid, item);
+ }
+ }
+ for (glyphid_t j = 0; j < cov->numGlyphs; j++) {
+ int gid = cov->glyphs[j].index;
+ coverage_entry *item = NULL;
+ HASH_FIND_INT(hash, &gid, item);
+ if (!item) {
+ NEW(item);
+ item->gid = gid;
+ item->covIndex = 0;
+ HASH_ADD_INT(hash, gid, item);
+ }
+ }
+ coverage_entry *e, *tmp;
+ HASH_ITER(hh, hash, e, tmp) {
+ pushClassDef(cd, Handle.fromIndex(e->gid), e->covIndex);
+ HASH_DEL(hash, e);
+ FREE(e);
+ }
+ otl_iClassDef.free(ocd);
+ return cd;
+}
+
+static json_value *dumpClassDef(const otl_ClassDef *cd) {
+ json_value *a = json_object_new(cd->numGlyphs);
+ for (glyphid_t j = 0; j < cd->numGlyphs; j++) {
+ json_object_push(a, cd->glyphs[j].name, json_integer_new(cd->classes[j]));
+ }
+ return preserialize(a);
+}
+
+static otl_ClassDef *parseClassDef(const json_value *_cd) {
+ if (!_cd || _cd->type != json_object) return NULL;
+ otl_ClassDef *cd = otl_iClassDef.create();
+ for (glyphid_t j = 0; j < _cd->u.object.length; j++) {
+ glyph_handle h = Handle.fromName(sdsnewlen(_cd->u.object.values[j].name, _cd->u.object.values[j].name_length));
+ json_value *_cid = _cd->u.object.values[j].value;
+ glyphclass_t cls = 0;
+ if (_cid->type == json_integer) {
+ cls = _cid->u.integer;
+ } else if (_cid->type == json_double) {
+ cls = _cid->u.dbl;
+ }
+ pushClassDef(cd, h, cls);
+ }
+ return cd;
+}
+
+typedef struct {
+ glyphid_t gid;
+ glyphclass_t cid;
+} classdef_sortrecord;
+
+static int by_gid(const void *a, const void *b) {
+ return ((const classdef_sortrecord *)a)->gid - ((const classdef_sortrecord *)b)->gid;
+}
+static caryll_Buffer *buildClassDef(const otl_ClassDef *cd) {
+ caryll_Buffer *buf = bufnew();
+ bufwrite16b(buf, 2);
+ if (!cd->numGlyphs) { // no glyphs, return a blank classdef
+ bufwrite16b(buf, 0);
+ return buf;
+ }
+
+ classdef_sortrecord *r;
+ NEW(r, cd->numGlyphs);
+ glyphid_t jj = 0;
+ for (glyphid_t j = 0; j < cd->numGlyphs; j++) {
+ if (cd->classes[j]) {
+ r[jj].gid = cd->glyphs[j].index;
+ r[jj].cid = cd->classes[j];
+ jj++;
+ }
+ }
+ if (!jj) { // The classdef has only class 0
+ FREE(r);
+ bufwrite16b(buf, 0);
+ return buf;
+ }
+ qsort(r, jj, sizeof(classdef_sortrecord), by_gid);
+
+ glyphid_t startGID = r[0].gid;
+ glyphid_t endGID = startGID;
+ glyphclass_t lastClass = r[0].cid;
+ glyphid_t nRanges = 0;
+ glyphid_t lastGID = startGID;
+ caryll_Buffer *ranges = bufnew();
+ for (glyphid_t j = 1; j < jj; j++) {
+ glyphid_t current = r[j].gid;
+ if (current <= lastGID) continue;
+ if (current == endGID + 1 && r[j].cid == lastClass) {
+ endGID = current;
+ } else {
+ bufwrite16b(ranges, startGID);
+ bufwrite16b(ranges, endGID);
+ bufwrite16b(ranges, lastClass);
+ nRanges += 1;
+ startGID = endGID = current;
+ lastClass = r[j].cid;
+ }
+ lastGID = current;
+ }
+ bufwrite16b(ranges, startGID);
+ bufwrite16b(ranges, endGID);
+ bufwrite16b(ranges, lastClass);
+ nRanges += 1;
+ bufwrite16b(buf, nRanges);
+ bufwrite_bufdel(buf, ranges);
+ FREE(r);
+ return buf;
+}
+
+static void shrinkClassDef(otl_ClassDef *cd) {
+ glyphid_t k = 0;
+ for (glyphid_t j = 0; j < cd->numGlyphs; j++) {
+ if (cd->glyphs[j].name) {
+ cd->glyphs[k] = cd->glyphs[j];
+ cd->classes[k] = cd->classes[j];
+ k++;
+ } else {
+ Handle.dispose(&cd->glyphs[j]);
+ }
+ }
+ cd->numGlyphs = k;
+}
+
+const struct __otfcc_IClassDef otl_iClassDef = {
+ caryll_standardRefTypeMethods(otl_ClassDef),
+ .read = readClassDef,
+ .expand = expandClassDef,
+ .dump = dumpClassDef,
+ .parse = parseClassDef,
+ .build = buildClassDef,
+ .shrink = shrinkClassDef,
+ .push = pushClassDef,
+};
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/constants.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/constants.c
new file mode 100644
index 00000000000..b9ea407ad06
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/constants.c
@@ -0,0 +1,24 @@
+#include "private.h"
+
+const char SCRIPT_LANGUAGE_SEPARATOR = '_';
+const char *lookupFlagsLabels[] = {"rightToLeft", "ignoreBases", "ignoreLigatures", "ignoreMarks", NULL};
+const char *tableNames[] = {[otl_type_unknown] = "unknown",
+ [otl_type_gsub_unknown] = "gsub_unknown",
+ [otl_type_gsub_single] = "gsub_single",
+ [otl_type_gsub_multiple] = "gsub_multiple",
+ [otl_type_gsub_alternate] = "gsub_alternate",
+ [otl_type_gsub_ligature] = "gsub_ligature",
+ [otl_type_gsub_context] = "gsub_context",
+ [otl_type_gsub_chaining] = "gsub_chaining",
+ [otl_type_gsub_extend] = "gsub_extend",
+ [otl_type_gsub_reverse] = "gsub_reverse",
+ [otl_type_gpos_unknown] = "gpos_unknown",
+ [otl_type_gpos_single] = "gpos_single",
+ [otl_type_gpos_pair] = "gpos_pair",
+ [otl_type_gpos_cursive] = "gpos_cursive",
+ [otl_type_gpos_markToBase] = "gpos_mark_to_base",
+ [otl_type_gpos_markToLigature] = "gpos_mark_to_ligature",
+ [otl_type_gpos_markToMark] = "gpos_mark_to_mark",
+ [otl_type_gpos_context] = "gpos_context",
+ [otl_type_gpos_chaining] = "gpos_chaining",
+ [otl_type_gpos_extend] = "gpos_extend"};
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/coverage.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/coverage.c
new file mode 100644
index 00000000000..f592d48d101
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/coverage.c
@@ -0,0 +1,256 @@
+#include "support/util.h"
+#include "otfcc/table/otl/coverage.h"
+
+static INLINE void disposeCoverage(MOVE otl_Coverage *coverage) {
+ for (glyphid_t j = 0; j < coverage->numGlyphs; j++) {
+ Handle.dispose(&coverage->glyphs[j]);
+ }
+ FREE(coverage->glyphs);
+}
+caryll_standardRefTypeFn(otl_Coverage, disposeCoverage);
+
+static void growCoverage(otl_Coverage *coverage, uint32_t n) {
+ if (!n) return;
+ if (n > coverage->capacity) {
+ if (!coverage->capacity) coverage->capacity = 0x10;
+ while (n > coverage->capacity)
+ coverage->capacity += (coverage->capacity >> 1) & 0xFFFFFF;
+ RESIZE(coverage->glyphs, coverage->capacity);
+ }
+}
+static void clearCoverage(otl_Coverage *coverage, uint32_t n) {
+ if (!coverage || !coverage->glyphs) return;
+ for (glyphid_t j = 0; j < coverage->numGlyphs; j++) {
+ Handle.dispose(&coverage->glyphs[j]);
+ }
+ growCoverage(coverage, n);
+ coverage->numGlyphs = n;
+}
+
+typedef struct {
+ int gid;
+ int covIndex;
+ UT_hash_handle hh;
+} coverage_entry;
+
+static int by_covIndex(coverage_entry *a, coverage_entry *b) {
+ return a->covIndex - b->covIndex;
+}
+
+static void pushToCoverage(otl_Coverage *coverage, MOVE otfcc_GlyphHandle h) {
+ coverage->numGlyphs += 1;
+ growCoverage(coverage, coverage->numGlyphs);
+ coverage->glyphs[coverage->numGlyphs - 1] = h;
+}
+
+static otl_Coverage *readCoverage(const uint8_t *data, uint32_t tableLength, uint32_t offset) {
+ otl_Coverage *coverage = otl_iCoverage.create();
+ if (tableLength < offset + 4) return coverage;
+ uint16_t format = read_16u(data + offset);
+ switch (format) {
+ case 1: {
+ uint16_t glyphCount = read_16u(data + offset + 2);
+ if (tableLength < offset + 4 + glyphCount * 2) return coverage;
+ coverage_entry *hash = NULL;
+ for (uint16_t j = 0; j < glyphCount; j++) {
+ coverage_entry *item = NULL;
+ int gid = read_16u(data + offset + 4 + j * 2);
+ HASH_FIND_INT(hash, &gid, item);
+ if (!item) {
+ NEW(item);
+ item->gid = gid;
+ item->covIndex = j;
+ HASH_ADD_INT(hash, gid, item);
+ }
+ }
+ HASH_SORT(hash, by_covIndex);
+ coverage_entry *e, *tmp;
+ HASH_ITER(hh, hash, e, tmp) {
+ pushToCoverage(coverage, Handle.fromIndex(e->gid));
+ HASH_DEL(hash, e);
+ FREE(e);
+ }
+ break;
+ }
+ case 2: {
+ uint16_t rangeCount = read_16u(data + offset + 2);
+ if (tableLength < offset + 4 + rangeCount * 6) return coverage;
+ coverage_entry *hash = NULL;
+ for (uint16_t j = 0; j < rangeCount; j++) {
+ uint16_t start = read_16u(data + offset + 4 + 6 * j);
+ uint16_t end = read_16u(data + offset + 4 + 6 * j + 2);
+ uint16_t startCoverageIndex = read_16u(data + offset + 4 + 6 * j + 4);
+ for (int k = start; k <= end; k++) {
+ coverage_entry *item = NULL;
+ HASH_FIND_INT(hash, &k, item);
+ if (!item) {
+ NEW(item);
+ item->gid = k;
+ item->covIndex = startCoverageIndex + k;
+ HASH_ADD_INT(hash, gid, item);
+ }
+ }
+ }
+ HASH_SORT(hash, by_covIndex);
+ coverage_entry *e, *tmp;
+ HASH_ITER(hh, hash, e, tmp) {
+ pushToCoverage(coverage, Handle.fromIndex(e->gid));
+ HASH_DEL(hash, e);
+ FREE(e);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return coverage;
+}
+
+static json_value *dumpCoverage(const otl_Coverage *coverage) {
+ json_value *a = json_array_new(coverage->numGlyphs);
+ for (glyphid_t j = 0; j < coverage->numGlyphs; j++) {
+ json_array_push(a, json_string_new(coverage->glyphs[j].name));
+ }
+ return preserialize(a);
+}
+
+static otl_Coverage *parseCoverage(const json_value *cov) {
+ otl_Coverage *c = otl_iCoverage.create();
+ if (!cov || cov->type != json_array) return c;
+
+ for (glyphid_t j = 0; j < cov->u.array.length; j++) {
+ if (cov->u.array.values[j]->type == json_string) {
+ pushToCoverage(c,
+ Handle.fromName(sdsnewlen(cov->u.array.values[j]->u.string.ptr,
+ cov->u.array.values[j]->u.string.length)));
+ }
+ }
+ return c;
+}
+
+static int by_gid(const void *a, const void *b) {
+ return *((const glyphid_t *)a) - *((const glyphid_t *)b);
+}
+
+static caryll_Buffer *buildCoverageFormat(const otl_Coverage *coverage, uint16_t format) {
+ // sort the gids in coverage
+ if (!coverage->numGlyphs) {
+ caryll_Buffer *buf = bufnew();
+ bufwrite16b(buf, 2);
+ bufwrite16b(buf, 0);
+ return buf;
+ }
+ glyphid_t *r;
+ NEW(r, coverage->numGlyphs);
+ glyphid_t jj = 0;
+ for (glyphid_t j = 0; j < coverage->numGlyphs; j++) {
+ r[jj] = coverage->glyphs[j].index;
+ jj++;
+ }
+ qsort(r, jj, sizeof(glyphid_t), by_gid);
+
+ caryll_Buffer *format1 = bufnew();
+ bufwrite16b(format1, 1);
+ bufwrite16b(format1, jj);
+ for (glyphid_t j = 0; j < jj; j++) {
+ bufwrite16b(format1, r[j]);
+ }
+ if (jj < 2) {
+ FREE(r);
+ return format1;
+ }
+
+ caryll_Buffer *format2 = bufnew();
+ bufwrite16b(format2, 2);
+ caryll_Buffer *ranges = bufnew();
+ glyphid_t startGID = r[0];
+ glyphid_t endGID = startGID;
+ glyphid_t lastGID = startGID;
+ glyphid_t nRanges = 0;
+ for (glyphid_t j = 1; j < jj; j++) {
+ glyphid_t current = r[j];
+ if (current <= lastGID) continue;
+ if (current == endGID + 1) {
+ endGID = current;
+ } else {
+ bufwrite16b(ranges, startGID);
+ bufwrite16b(ranges, endGID);
+ bufwrite16b(ranges, j + startGID - endGID - 1);
+ nRanges += 1;
+ startGID = endGID = current;
+ }
+ lastGID = current;
+ }
+ bufwrite16b(ranges, startGID);
+ bufwrite16b(ranges, endGID);
+ bufwrite16b(ranges, jj + startGID - endGID - 1);
+ nRanges += 1;
+ bufwrite16b(format2, nRanges);
+ bufwrite_bufdel(format2, ranges);
+
+ if (format == 1) {
+ buffree(format2);
+ FREE(r);
+ return format1;
+ } else if (format == 2) {
+ buffree(format1);
+ FREE(r);
+ return format2;
+ } else {
+ if (buflen(format1) < buflen(format2)) {
+ buffree(format2);
+ FREE(r);
+ return format1;
+ } else {
+ buffree(format1);
+ FREE(r);
+ return format2;
+ }
+ }
+}
+
+static caryll_Buffer *buildCoverage(const otl_Coverage *coverage) {
+ return buildCoverageFormat(coverage, 0);
+}
+
+static int byHandleGID(const void *a, const void *b) {
+ return ((const glyph_handle *)a)->index - ((const glyph_handle *)b)->index;
+}
+
+static void shrinkCoverage(otl_Coverage *coverage, bool dosort) {
+ if (!coverage) return;
+ glyphid_t k = 0;
+ for (glyphid_t j = 0; j < coverage->numGlyphs; j++) {
+ if (coverage->glyphs[j].name) {
+ coverage->glyphs[k++] = coverage->glyphs[j];
+ } else {
+ Handle.dispose(&coverage->glyphs[j]);
+ }
+ }
+ if (dosort) {
+ qsort(coverage->glyphs, k, sizeof(glyph_handle), byHandleGID);
+ glyphid_t skip = 0;
+ for (glyphid_t rear = 1; rear < k; rear++) {
+ if (coverage->glyphs[rear].index == coverage->glyphs[rear - skip - 1].index) {
+ Handle.dispose(&coverage->glyphs[rear]);
+ skip += 1;
+ } else {
+ coverage->glyphs[rear - skip] = coverage->glyphs[rear];
+ }
+ }
+ k -= skip;
+ }
+ coverage->numGlyphs = k;
+}
+
+const struct __otfcc_ICoverage otl_iCoverage = {
+ caryll_standardRefTypeMethods(otl_Coverage),
+ .clear = clearCoverage,
+ .read = readCoverage,
+ .dump = dumpCoverage,
+ .parse = parseCoverage,
+ .build = buildCoverage,
+ .buildFormat = buildCoverageFormat,
+ .shrink = shrinkCoverage,
+ .push = pushToCoverage,
+};
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/dump.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/dump.c
new file mode 100644
index 00000000000..60d5f99b282
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/dump.c
@@ -0,0 +1,87 @@
+#include "private.h"
+
+static void _declare_lookup_dumper(otl_LookupType llt, const char *lt, json_value *(*dumper)(const otl_Subtable *st),
+ otl_Lookup *lookup, json_value *dump) {
+ if (lookup->type == llt) {
+ json_object_push(dump, "type", json_string_new(lt));
+ json_object_push(dump, "flags", otfcc_dump_flags(lookup->flags, lookupFlagsLabels));
+ if (lookup->flags >> 8) { json_object_push(dump, "markAttachmentType", json_integer_new(lookup->flags >> 8)); }
+ json_value *subtables = json_array_new(lookup->subtables.length);
+ for (tableid_t j = 0; j < lookup->subtables.length; j++)
+ if (lookup->subtables.items[j]) { json_array_push(subtables, dumper(lookup->subtables.items[j])); }
+ json_object_push(dump, "subtables", subtables);
+ }
+}
+
+#define LOOKUP_DUMPER(llt, fn) _declare_lookup_dumper(llt, tableNames[llt], fn, lookup, dump);
+
+static void _dump_lookup(otl_Lookup *lookup, json_value *dump) {
+ LOOKUP_DUMPER(otl_type_gsub_single, otl_gsub_dump_single);
+ LOOKUP_DUMPER(otl_type_gsub_multiple, otl_gsub_dump_multi);
+ LOOKUP_DUMPER(otl_type_gsub_alternate, otl_gsub_dump_multi);
+ LOOKUP_DUMPER(otl_type_gsub_ligature, otl_gsub_dump_ligature);
+ LOOKUP_DUMPER(otl_type_gsub_chaining, otl_dump_chaining);
+ LOOKUP_DUMPER(otl_type_gsub_reverse, otl_gsub_dump_reverse);
+ LOOKUP_DUMPER(otl_type_gpos_chaining, otl_dump_chaining);
+ LOOKUP_DUMPER(otl_type_gpos_single, otl_gpos_dump_single);
+ LOOKUP_DUMPER(otl_type_gpos_pair, otl_gpos_dump_pair);
+ LOOKUP_DUMPER(otl_type_gpos_cursive, otl_gpos_dump_cursive);
+ LOOKUP_DUMPER(otl_type_gpos_markToBase, otl_gpos_dump_markToSingle);
+ LOOKUP_DUMPER(otl_type_gpos_markToMark, otl_gpos_dump_markToSingle);
+ LOOKUP_DUMPER(otl_type_gpos_markToLigature, otl_gpos_dump_markToLigature);
+}
+
+void otfcc_dumpOtl(const table_OTL *table, json_value *root, const otfcc_Options *options, const char *tag) {
+ if (!table || !table->languages.length || !table->lookups.length || !table->features.length) return;
+ loggedStep("%s", tag) {
+ json_value *otl = json_object_new(3);
+ loggedStep("Languages") {
+ // dump script list
+ json_value *languages = json_object_new(table->languages.length);
+ for (tableid_t j = 0; j < table->languages.length; j++) {
+ json_value *_lang = json_object_new(5);
+ otl_LanguageSystem *lang = table->languages.items[j];
+ if (lang->requiredFeature) {
+ json_object_push(_lang, "requiredFeature", json_string_new(lang->requiredFeature->name));
+ }
+ json_value *features = json_array_new(lang->features.length);
+ for (tableid_t k = 0; k < lang->features.length; k++)
+ if (lang->features.items[k]) {
+ json_array_push(features, json_string_new(lang->features.items[k]->name));
+ }
+ json_object_push(_lang, "features", preserialize(features));
+ json_object_push(languages, lang->name, _lang);
+ }
+ json_object_push(otl, "languages", languages);
+ }
+ loggedStep("Features") {
+ // dump feature list
+ json_value *features = json_object_new(table->features.length);
+ for (tableid_t j = 0; j < table->features.length; j++) {
+ otl_Feature *feature = table->features.items[j];
+ json_value *_feature = json_array_new(feature->lookups.length);
+ for (tableid_t k = 0; k < feature->lookups.length; k++)
+ if (feature->lookups.items[k]) {
+ json_array_push(_feature, json_string_new(feature->lookups.items[k]->name));
+ }
+ json_object_push(features, feature->name, preserialize(_feature));
+ }
+ json_object_push(otl, "features", features);
+ }
+ loggedStep("Lookups") {
+ // dump lookups
+ json_value *lookups = json_object_new(table->lookups.length);
+ json_value *lookupOrder = json_array_new(table->lookups.length);
+ for (tableid_t j = 0; j < table->lookups.length; j++) {
+ json_value *_lookup = json_object_new(5);
+ otl_Lookup *lookup = table->lookups.items[j];
+ _dump_lookup(lookup, _lookup);
+ json_object_push(lookups, lookup->name, _lookup);
+ json_array_push(lookupOrder, json_string_new(lookup->name));
+ }
+ json_object_push(otl, "lookups", lookups);
+ json_object_push(otl, "lookupOrder", lookupOrder);
+ }
+ json_object_push(root, tag, otl);
+ }
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/otl.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/otl.c
new file mode 100644
index 00000000000..eba22583124
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/otl.c
@@ -0,0 +1,104 @@
+#include "private.h"
+
+#define DELETE_TYPE(type, fn) \
+ case type: \
+ ((void (*)(otl_Subtable *))fn)(*subtableRef); \
+ break;
+
+static INLINE void disposeSubtableDependent(MODIFY otl_SubtablePtr *subtableRef,
+ const otl_Lookup *lookup) {
+ switch (lookup->type) {
+ DELETE_TYPE(otl_type_gsub_single, iSubtable_gsub_single.free);
+ DELETE_TYPE(otl_type_gsub_multiple, iSubtable_gsub_multi.free);
+ DELETE_TYPE(otl_type_gsub_alternate, iSubtable_gsub_multi.free);
+ DELETE_TYPE(otl_type_gsub_ligature, iSubtable_gsub_ligature.free);
+ DELETE_TYPE(otl_type_gsub_chaining, iSubtable_chaining.free);
+ DELETE_TYPE(otl_type_gsub_reverse, iSubtable_gsub_reverse.free);
+ DELETE_TYPE(otl_type_gpos_single, iSubtable_gpos_single.free);
+ DELETE_TYPE(otl_type_gpos_pair, iSubtable_gpos_pair.free);
+ DELETE_TYPE(otl_type_gpos_cursive, iSubtable_gpos_cursive.free);
+ DELETE_TYPE(otl_type_gpos_chaining, iSubtable_chaining.free);
+ DELETE_TYPE(otl_type_gpos_markToBase, iSubtable_gpos_markToSingle.free);
+ DELETE_TYPE(otl_type_gpos_markToMark, iSubtable_gpos_markToSingle.free);
+ DELETE_TYPE(otl_type_gpos_markToLigature, iSubtable_gpos_markToLigature.free);
+ default:;
+ }
+}
+static caryll_ElementInterface(otl_SubtablePtr) otl_iSubtablePtr = {
+ .init = NULL, .copy = NULL, .dispose = NULL,
+};
+caryll_VectorImplFreeDependent(otl_SubtableList, otl_SubtablePtr, otl_Lookup,
+ disposeSubtableDependent);
+caryll_VectorImplFunctions(otl_SubtableList, otl_SubtablePtr, otl_iSubtablePtr);
+caryll_VectorInterfaceTypeName(otl_SubtableList) otl_iSubtableList = {
+ caryll_VectorImplAssignments(otl_SubtableList, otl_SubtablePtr, otl_iSubtablePtr),
+ .disposeDependent = otl_SubtableList_disposeDependent,
+};
+
+void otfcc_delete_lookup(otl_Lookup *lookup) {
+ if (!lookup) return;
+ otl_iSubtableList.disposeDependent(&lookup->subtables, lookup);
+ sdsfree(lookup->name);
+ FREE(lookup);
+}
+
+// LOOKUP
+static INLINE void initLookupPtr(otl_LookupPtr *entry) {
+ NEW(*entry);
+ (*entry)->name = NULL;
+ otl_iSubtableList.init(&(*entry)->subtables);
+}
+static INLINE void disposeLookupPtr(otl_LookupPtr *entry) {
+ otfcc_delete_lookup(*entry);
+}
+caryll_standardType(otl_LookupPtr, otl_iLookupPtr, initLookupPtr, disposeLookupPtr);
+caryll_standardVectorImpl(otl_LookupList, otl_LookupPtr, otl_iLookupPtr, otl_iLookupList);
+caryll_standardType(otl_LookupRef, otl_iLookupRef);
+caryll_standardVectorImpl(otl_LookupRefList, otl_LookupRef, otl_iLookupRef, otl_iLookupRefList);
+
+// FEATURE
+static INLINE void initFeaturePtr(otl_FeaturePtr *feature) {
+ NEW(*feature);
+ otl_iLookupRefList.init(&(*feature)->lookups);
+}
+static INLINE void disposeFeaturePtr(otl_FeaturePtr *feature) {
+ if (!*feature) return;
+ if ((*feature)->name) sdsfree((*feature)->name);
+ otl_iLookupRefList.dispose(&(*feature)->lookups);
+ FREE(*feature);
+}
+caryll_standardType(otl_FeaturePtr, otl_iFeaturePtr, initFeaturePtr, disposeFeaturePtr);
+caryll_standardVectorImpl(otl_FeatureList, otl_FeaturePtr, otl_iFeaturePtr, otl_iFeatureList);
+caryll_standardType(otl_FeatureRef, otl_iFeatureRef);
+caryll_standardVectorImpl(otl_FeatureRefList, otl_FeatureRef, otl_iFeatureRef, otl_iFeatureRefList);
+
+// LANGUAGE
+static INLINE void initLanguagePtr(otl_LanguageSystemPtr *language) {
+ NEW(*language);
+ otl_iFeatureRefList.init(&(*language)->features);
+}
+static INLINE void disposeLanguagePtr(otl_LanguageSystemPtr *language) {
+ if (!*language) return;
+ if ((*language)->name) sdsfree((*language)->name);
+ otl_iFeatureRefList.dispose(&(*language)->features);
+ FREE(*language);
+}
+caryll_ElementInterfaceOf(otl_LanguageSystemPtr) otl_iLanguageSystem = {
+ .init = initLanguagePtr, .dispose = disposeLanguagePtr,
+};
+caryll_standardVectorImpl(otl_LangSystemList, otl_LanguageSystemPtr, otl_iLanguageSystem,
+ otl_iLangSystemList);
+
+// COMMON PART
+static INLINE void initOTL(table_OTL *table) {
+ otl_iLookupList.init(&table->lookups);
+ otl_iFeatureList.init(&table->features);
+ otl_iLangSystemList.init(&table->languages);
+}
+static INLINE void disposeOTL(table_OTL *table) {
+ otl_iLookupList.dispose(&table->lookups);
+ otl_iFeatureList.dispose(&table->features);
+ otl_iLangSystemList.dispose(&table->languages);
+}
+
+caryll_standardRefType(table_OTL, table_iOTL, initOTL, disposeOTL);
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/parse.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/parse.c
new file mode 100644
index 00000000000..f02b3746d21
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/parse.c
@@ -0,0 +1,376 @@
+#include "private.h"
+
+typedef enum { LOOKUP_ORDER_FORCE, LOOKUP_ORDER_FILE } lookup_order_type;
+
+typedef struct {
+ char *name;
+ otl_Lookup *lookup;
+ UT_hash_handle hh;
+ lookup_order_type orderType;
+ uint16_t orderVal;
+} lookup_hash;
+
+typedef struct {
+ char *name;
+ bool alias;
+ otl_Feature *feature;
+ UT_hash_handle hh;
+} feature_hash;
+
+typedef struct {
+ char *name;
+ otl_LanguageSystem *language;
+ UT_hash_handle hh;
+} language_hash;
+static bool _declareLookupParser(const char *lt, otl_LookupType llt,
+ otl_Subtable *(*parser)(const json_value *,
+ const otfcc_Options *options),
+ json_value *_lookup, char *lookupName,
+ const otfcc_Options *options, lookup_hash **lh);
+
+#define LOOKUP_PARSER(llt, parser) \
+ if (!parsed) { \
+ parsed = \
+ _declareLookupParser(tableNames[llt], llt, parser, lookup, lookupName, options, lh); \
+ }
+
+static bool _parse_lookup(json_value *lookup, char *lookupName, const otfcc_Options *options,
+ lookup_hash **lh) {
+ bool parsed = false;
+ LOOKUP_PARSER(otl_type_gsub_single, otl_gsub_parse_single);
+ LOOKUP_PARSER(otl_type_gsub_multiple, otl_gsub_parse_multi);
+ LOOKUP_PARSER(otl_type_gsub_alternate, otl_gsub_parse_multi);
+ LOOKUP_PARSER(otl_type_gsub_ligature, otl_gsub_parse_ligature);
+ LOOKUP_PARSER(otl_type_gsub_chaining, otl_parse_chaining);
+ LOOKUP_PARSER(otl_type_gsub_reverse, otl_gsub_parse_reverse);
+ LOOKUP_PARSER(otl_type_gpos_single, otl_gpos_parse_single);
+ LOOKUP_PARSER(otl_type_gpos_pair, otl_gpos_parse_pair);
+ LOOKUP_PARSER(otl_type_gpos_cursive, otl_gpos_parse_cursive);
+ LOOKUP_PARSER(otl_type_gpos_chaining, otl_parse_chaining);
+ LOOKUP_PARSER(otl_type_gpos_markToBase, otl_gpos_parse_markToSingle);
+ LOOKUP_PARSER(otl_type_gpos_markToMark, otl_gpos_parse_markToSingle);
+ LOOKUP_PARSER(otl_type_gpos_markToLigature, otl_gpos_parse_markToLigature);
+ return parsed;
+}
+
+static bool _declareLookupParser(const char *lt, otl_LookupType llt,
+ otl_Subtable *(*parser)(const json_value *,
+ const otfcc_Options *options),
+ json_value *_lookup, char *lookupName,
+ const otfcc_Options *options, lookup_hash **lh) {
+
+ // detect a valid type field exists
+ json_value *type = json_obj_get_type(_lookup, "type", json_string);
+ if (!type || strcmp(type->u.string.ptr, lt)) {
+ if (!type) logWarning("Lookup %s does not have a valid 'type' field.", lookupName);
+ return false;
+ }
+ // no duplicate lookup has been parsed
+ lookup_hash *item = NULL;
+ HASH_FIND_STR(*lh, lookupName, item);
+ if (item) {
+ logWarning("Lookup %s already exists.", lookupName);
+ return false;
+ }
+ // detect valid subtables array
+ json_value *_subtables = json_obj_get_type(_lookup, "subtables", json_array);
+ if (!_subtables) {
+ logWarning("Lookup %s does not have a valid subtable list.", lookupName);
+ return false;
+ }
+ // init this lookup
+ otl_Lookup *lookup;
+ otl_iLookupPtr.init(&lookup);
+ lookup->type = llt;
+ lookup->flags = otfcc_parse_flags(json_obj_get(_lookup, "flags"), lookupFlagsLabels);
+ uint16_t markAttachmentType = json_obj_getint(_lookup, "markAttachmentType");
+ if (markAttachmentType) { lookup->flags |= markAttachmentType << 8; }
+ // start parse subtables
+ tableid_t subtableCount = _subtables->u.array.length;
+ loggedStep("%s", lookupName) {
+ for (tableid_t j = 0; j < subtableCount; j++) {
+ json_value *_subtable = _subtables->u.array.values[j];
+ if (_subtable && _subtable->type == json_object) {
+ otl_Subtable *_st = parser(_subtable, options);
+ otl_iSubtableList.push(&lookup->subtables, _st);
+ }
+ }
+ }
+ if (!lookup->subtables.length) {
+ logWarning("Lookup %s does not have any subtables.", lookupName);
+ otfcc_delete_lookup(lookup);
+ return false;
+ }
+ // we've found a valid lookup, put it into the hash.
+ NEW(item);
+ item->name = sdsnew(lookupName);
+ lookup->name = sdsdup(item->name);
+ item->lookup = lookup;
+ item->orderType = LOOKUP_ORDER_FILE;
+ item->orderVal = HASH_COUNT(*lh);
+ HASH_ADD_STR(*lh, name, item);
+ return true;
+}
+
+static lookup_hash *figureOutLookupsFromJSON(json_value *lookups, const otfcc_Options *options) {
+ lookup_hash *lh = NULL;
+
+ for (uint32_t j = 0; j < lookups->u.object.length; j++) {
+ char *lookupName = lookups->u.object.values[j].name;
+ if (lookups->u.object.values[j].value->type == json_object) {
+ bool parsed =
+ _parse_lookup(lookups->u.object.values[j].value, lookupName, options, &lh);
+ if (!parsed) {
+ logWarning("[OTFCC-fea] Ignoring invalid or unsupported lookup %s.\n", lookupName);
+ }
+ } else if (lookups->u.object.values[j].value->type == json_string) {
+ char *thatname = lookups->u.object.values[j].value->u.string.ptr;
+ lookup_hash *s = NULL;
+ HASH_FIND_STR(lh, thatname, s);
+ if (s) {
+ lookup_hash *dup;
+ NEW(dup);
+ dup->name = sdsnew(lookupName);
+ dup->lookup = s->lookup;
+ dup->orderType = LOOKUP_ORDER_FILE;
+ dup->orderVal = HASH_COUNT(lh);
+ HASH_ADD_STR(lh, name, dup);
+ }
+ }
+ }
+ return lh;
+}
+
+static void feature_merger_activate(json_value *d, const bool sametag, const char *objtype,
+ const otfcc_Options *options) {
+ for (uint32_t j = 0; j < d->u.object.length; j++) {
+ json_value *jthis = d->u.object.values[j].value;
+ char *kthis = d->u.object.values[j].name;
+ uint32_t nkthis = d->u.object.values[j].name_length;
+ if (jthis->type != json_array && jthis->type != json_object) continue;
+ for (uint32_t k = j + 1; k < d->u.object.length; k++) {
+ json_value *jthat = d->u.object.values[k].value;
+ char *kthat = d->u.object.values[k].name;
+ if (json_ident(jthis, jthat) && (sametag ? strncmp(kthis, kthat, 4) == 0 : true)) {
+ json_value_free(jthat);
+ json_value *v = json_string_new_length(nkthis, kthis);
+ v->parent = d;
+ d->u.object.values[k].value = v;
+ logNotice("[OTFCC-fea] Merged duplicate %s '%s' into '%s'.\n", objtype, kthat,
+ kthis);
+ }
+ }
+ }
+}
+
+static feature_hash *figureOutFeaturesFromJSON(json_value *features, lookup_hash *lh,
+ const char *tag, const otfcc_Options *options) {
+ feature_hash *fh = NULL;
+ // Remove duplicates
+ if (options->merge_features) { feature_merger_activate(features, true, "feature", options); }
+ // Resolve features
+ for (uint32_t j = 0; j < features->u.object.length; j++) {
+ char *featureName = features->u.object.values[j].name;
+ json_value *_feature = features->u.object.values[j].value;
+ if (_feature->type == json_array) {
+ otl_LookupRefList al;
+ otl_iLookupRefList.init(&al);
+ for (tableid_t k = 0; k < _feature->u.array.length; k++) {
+ json_value *term = _feature->u.array.values[k];
+ if (term->type != json_string) continue;
+ lookup_hash *item = NULL;
+ HASH_FIND_STR(lh, term->u.string.ptr, item);
+ if (item) {
+ otl_iLookupRefList.push(&al, item->lookup);
+ } else {
+ logWarning("Lookup assignment %s for feature [%s/%s] is missing or invalid.",
+ term->u.string.ptr, tag, featureName)
+ }
+ }
+ if (al.length > 0) {
+ feature_hash *s = NULL;
+ HASH_FIND_STR(fh, featureName, s);
+ if (!s) {
+ NEW(s);
+ s->name = sdsnew(featureName);
+ s->alias = false;
+ otl_iFeaturePtr.init(&s->feature);
+ s->feature->name = sdsdup(s->name);
+ otl_iLookupRefList.replace(&s->feature->lookups, al);
+ HASH_ADD_STR(fh, name, s);
+ } else {
+ logWarning("[OTFCC-fea] Duplicate feature for [%s/%s]. This feature will "
+ "be ignored.\n",
+ tag, featureName);
+ otl_iLookupRefList.dispose(&al);
+ }
+ } else {
+ logWarning("[OTFCC-fea] There is no valid lookup "
+ "assignments for [%s/%s]. This feature will be "
+ "ignored.\n",
+ tag, featureName);
+ otl_iLookupRefList.dispose(&al);
+ }
+ } else if (_feature->type == json_string) {
+ feature_hash *s = NULL;
+ char *target = _feature->u.string.ptr;
+ HASH_FIND_STR(fh, target, s);
+ if (s) {
+ feature_hash *dup;
+ NEW(dup);
+ dup->alias = true;
+ dup->name = sdsnew(featureName);
+ dup->feature = s->feature;
+ HASH_ADD_STR(fh, name, dup);
+ }
+ }
+ }
+ return fh;
+}
+static bool isValidLanguageName(const char *name, const size_t length) {
+ return length == 9 && name[4] == SCRIPT_LANGUAGE_SEPARATOR;
+}
+static language_hash *figureOutLanguagesFromJson(json_value *languages, feature_hash *fh,
+ const char *tag, const otfcc_Options *options) {
+ language_hash *sh = NULL;
+ // languages
+ for (uint32_t j = 0; j < languages->u.object.length; j++) {
+ char *languageName = languages->u.object.values[j].name;
+ size_t languageNameLen = languages->u.object.values[j].name_length;
+ json_value *_language = languages->u.object.values[j].value;
+ if (isValidLanguageName(languageName, languageNameLen) && _language->type == json_object) {
+ otl_Feature *requiredFeature = NULL;
+ json_value *_rf = json_obj_get_type(_language, "requiredFeature", json_string);
+ if (_rf) {
+ // required feature term
+ feature_hash *rf = NULL;
+ HASH_FIND_STR(fh, _rf->u.string.ptr, rf);
+ if (rf) { requiredFeature = rf->feature; }
+ }
+ otl_FeatureRefList af;
+ otl_iFeatureRefList.init(&af);
+ json_value *_features = json_obj_get_type(_language, "features", json_array);
+ if (_features) {
+ for (tableid_t k = 0; k < _features->u.array.length; k++) {
+ json_value *term = _features->u.array.values[k];
+ if (term->type == json_string) {
+ feature_hash *item = NULL;
+ HASH_FIND_STR(fh, term->u.string.ptr, item);
+ if (item) { otl_iFeatureRefList.push(&af, item->feature); }
+ }
+ }
+ }
+ if (requiredFeature || (af.length > 0)) {
+ language_hash *s = NULL;
+ HASH_FIND_STR(sh, languageName, s);
+ if (!s) {
+ NEW(s);
+ s->name = sdsnew(languageName);
+ otl_iLanguageSystem.init(&s->language);
+ s->language->name = sdsdup(s->name);
+ s->language->requiredFeature = requiredFeature;
+ otl_iFeatureRefList.replace(&s->language->features, af);
+ HASH_ADD_STR(sh, name, s);
+ } else {
+ logWarning("[OTFCC-fea] Duplicate language item [%s/%s]. This language "
+ "term will be ignored.\n",
+ tag, languageName);
+ otl_iFeatureRefList.dispose(&af);
+ }
+ } else {
+ logWarning("[OTFCC-fea] There is no valid feature "
+ "assignments for [%s/%s]. This language term "
+ "will be ignored.\n",
+ tag, languageName);
+ otl_iFeatureRefList.dispose(&af);
+ }
+ }
+ }
+ return sh;
+}
+
+static int by_lookup_order(lookup_hash *a, lookup_hash *b) {
+ if (a->orderType == b->orderType) {
+ return a->orderVal - b->orderVal;
+ } else {
+ return a->orderType - b->orderType;
+ }
+}
+static int by_feature_name(feature_hash *a, feature_hash *b) {
+ return strcmp(a->name, b->name);
+}
+static int by_language_name(language_hash *a, language_hash *b) {
+ return strcmp(a->name, b->name);
+}
+table_OTL *otfcc_parseOtl(const json_value *root, const otfcc_Options *options, const char *tag) {
+ table_OTL *otl = NULL;
+ json_value *table = json_obj_get_type(root, tag, json_object);
+ if (!table) goto FAIL;
+ otl = table_iOTL.create();
+ json_value *languages = json_obj_get_type(table, "languages", json_object);
+ json_value *features = json_obj_get_type(table, "features", json_object);
+ json_value *lookups = json_obj_get_type(table, "lookups", json_object);
+ if (!languages || !features || !lookups) goto FAIL;
+
+ loggedStep("%s", tag) {
+ lookup_hash *lh = figureOutLookupsFromJSON(lookups, options);
+ json_value *lookupOrder = json_obj_get_type(table, "lookupOrder", json_array);
+ if (lookupOrder) {
+ for (tableid_t j = 0; j < lookupOrder->u.array.length; j++) {
+ json_value *_ln = lookupOrder->u.array.values[j];
+ if (_ln && _ln->type == json_string) {
+ lookup_hash *item = NULL;
+ HASH_FIND_STR(lh, _ln->u.string.ptr, item);
+ if (item) {
+ item->orderType = LOOKUP_ORDER_FORCE;
+ item->orderVal = j;
+ }
+ }
+ }
+ }
+ HASH_SORT(lh, by_lookup_order);
+ feature_hash *fh = figureOutFeaturesFromJSON(features, lh, tag, options);
+ HASH_SORT(fh, by_feature_name);
+ language_hash *sh = figureOutLanguagesFromJson(languages, fh, tag, options);
+ HASH_SORT(sh, by_language_name);
+ if (!HASH_COUNT(lh) || !HASH_COUNT(fh) || !HASH_COUNT(sh)) {
+ options->logger->dedent(options->logger);
+ goto FAIL;
+ }
+
+ {
+ lookup_hash *s, *tmp;
+ HASH_ITER(hh, lh, s, tmp) {
+ otl_iLookupList.push(&otl->lookups, s->lookup);
+ HASH_DEL(lh, s);
+ sdsfree(s->name);
+ FREE(s);
+ }
+ }
+ {
+ feature_hash *s, *tmp;
+ HASH_ITER(hh, fh, s, tmp) {
+ if (!s->alias) { otl_iFeatureList.push(&otl->features, s->feature); }
+ HASH_DEL(fh, s);
+ sdsfree(s->name);
+ FREE(s);
+ }
+ }
+ {
+ language_hash *s, *tmp;
+ HASH_ITER(hh, sh, s, tmp) {
+ otl_iLangSystemList.push(&otl->languages, s->language);
+ HASH_DEL(sh, s);
+ sdsfree(s->name);
+ FREE(s);
+ }
+ }
+ }
+ return otl;
+FAIL:
+ if (otl) {
+ logWarning("[OTFCC-fea] Ignoring invalid or incomplete OTL table %s.\n", tag);
+ table_iOTL.free(otl);
+ }
+ return NULL;
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/private.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/private.h
new file mode 100644
index 00000000000..3e72b77face
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/private.h
@@ -0,0 +1,24 @@
+#ifndef CARYLL_TABLE_OTL_PRIVATE_H
+#define CARYLL_TABLE_OTL_PRIVATE_H
+
+#include "subtables/gsub-single.h"
+#include "subtables/gsub-multi.h"
+#include "subtables/gsub-ligature.h"
+#include "subtables/gsub-reverse.h"
+#include "subtables/gpos-single.h"
+#include "subtables/gpos-pair.h"
+#include "subtables/gpos-cursive.h"
+#include "subtables/gpos-mark-to-single.h"
+#include "subtables/gpos-mark-to-ligature.h"
+#include "subtables/chaining.h"
+#include "subtables/extend.h"
+
+extern const char SCRIPT_LANGUAGE_SEPARATOR;
+extern const char *lookupFlagsLabels[];
+extern const char *tableNames[];
+
+void otfcc_delete_lookup(otl_Lookup *lookup);
+
+#define OTFCC_CHR(a,b,c,d) ( ((a)<<24) | ((b)<<16) | ((c)<<8) | (d) )
+
+#endif
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/read.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/read.c
new file mode 100644
index 00000000000..f1fd1f9230b
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/read.c
@@ -0,0 +1,267 @@
+#include "private.h"
+
+#define LOOKUP_READER(llt, fn) \
+ case llt: \
+ return fn(data, tableLength, subtableOffset, maxGlyphs, options);
+
+otl_Subtable *otfcc_readOtl_subtable(font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, otl_LookupType lookupType,
+ const glyphid_t maxGlyphs, const otfcc_Options *options) {
+ switch (lookupType) {
+ LOOKUP_READER(otl_type_gsub_single, otl_read_gsub_single);
+ LOOKUP_READER(otl_type_gsub_multiple, otl_read_gsub_multi);
+ LOOKUP_READER(otl_type_gsub_alternate, otl_read_gsub_multi);
+ LOOKUP_READER(otl_type_gsub_ligature, otl_read_gsub_ligature);
+ LOOKUP_READER(otl_type_gsub_chaining, otl_read_chaining);
+ LOOKUP_READER(otl_type_gsub_reverse, otl_read_gsub_reverse);
+ LOOKUP_READER(otl_type_gpos_chaining, otl_read_chaining);
+ LOOKUP_READER(otl_type_gsub_context, otl_read_contextual);
+ LOOKUP_READER(otl_type_gpos_context, otl_read_contextual);
+ LOOKUP_READER(otl_type_gpos_single, otl_read_gpos_single);
+ LOOKUP_READER(otl_type_gpos_pair, otl_read_gpos_pair);
+ LOOKUP_READER(otl_type_gpos_cursive, otl_read_gpos_cursive);
+ LOOKUP_READER(otl_type_gpos_markToBase, otl_read_gpos_markToSingle);
+ LOOKUP_READER(otl_type_gpos_markToMark, otl_read_gpos_markToSingle);
+ LOOKUP_READER(otl_type_gpos_markToLigature, otl_read_gpos_markToLigature);
+ LOOKUP_READER(otl_type_gsub_extend, otfcc_readOtl_gsub_extend);
+ LOOKUP_READER(otl_type_gpos_extend, otfcc_readOtl_gpos_extend);
+ default:
+ return NULL;
+ }
+}
+
+static void parseLanguage(font_file_pointer data, uint32_t tableLength, uint32_t base,
+ otl_LanguageSystem *lang, otl_FeatureList *features) {
+ checkLength(base + 6);
+ tableid_t rid = read_16u(data + base + 2);
+ if (rid < features->length) {
+ lang->requiredFeature = features->items[rid];
+ } else {
+ lang->requiredFeature = NULL;
+ }
+ tableid_t featureCount = read_16u(data + base + 4);
+ for (tableid_t j = 0; j < featureCount; j++) {
+ tableid_t featureIndex = read_16u(data + base + 6 + 2 * j);
+ if (featureIndex < features->length) {
+ otl_iFeatureRefList.push(&lang->features, features->items[featureIndex]);
+ }
+ }
+ return;
+FAIL:
+ otl_iFeatureRefList.dispose(&lang->features);
+ lang->requiredFeature = NULL;
+ return;
+}
+
+static table_OTL *otfcc_readOtl_common(font_file_pointer data, uint32_t tableLength,
+ otl_LookupType lookup_type_base,
+ const otfcc_Options *options) {
+ table_OTL *table = table_iOTL.create();
+ if (!table) goto FAIL;
+ checkLength(10);
+ uint32_t scriptListOffset = read_16u(data + 4);
+ checkLength(scriptListOffset + 2);
+ uint32_t featureListOffset = read_16u(data + 6);
+ checkLength(featureListOffset + 2);
+ uint32_t lookupListOffset = read_16u(data + 8);
+ checkLength(lookupListOffset + 2);
+
+ // parse lookup list
+ {
+ tableid_t lookupCount = read_16u(data + lookupListOffset);
+ checkLength(lookupListOffset + 2 + lookupCount * 2);
+ for (tableid_t j = 0; j < lookupCount; j++) {
+ otl_Lookup *lookup;
+ otl_iLookupPtr.init(&lookup);
+ lookup->_offset = lookupListOffset + read_16u(data + lookupListOffset + 2 + 2 * j);
+ checkLength(lookup->_offset + 6);
+ lookup->type = read_16u(data + lookup->_offset) + lookup_type_base;
+ otl_iLookupList.push(&table->lookups, lookup);
+ }
+ }
+
+ // parse feature list
+ {
+ tableid_t featureCount = read_16u(data + featureListOffset);
+ checkLength(featureListOffset + 2 + featureCount * 6);
+ tableid_t lnk = 0;
+ for (tableid_t j = 0; j < featureCount; j++) {
+ otl_Feature *feature;
+ otl_iFeaturePtr.init(&feature);
+ uint32_t tag = read_32u(data + featureListOffset + 2 + j * 6);
+ if (options->glyph_name_prefix) {
+ feature->name = sdscatprintf(sdsempty(), "%c%c%c%c_%s_%05d", (tag >> 24) & 0xFF,
+ (tag >> 16) & 0xFF, (tag >> 8) & 0xff, tag & 0xff,
+ options->glyph_name_prefix, j);
+ } else {
+ feature->name = sdscatprintf(sdsempty(), "%c%c%c%c_%05d", (tag >> 24) & 0xFF,
+ (tag >> 16) & 0xFF, (tag >> 8) & 0xff, tag & 0xff, j);
+ }
+ uint32_t featureOffset =
+ featureListOffset + read_16u(data + featureListOffset + 2 + j * 6 + 4);
+
+ checkLength(featureOffset + 4);
+ tableid_t lookupCount = read_16u(data + featureOffset + 2);
+ checkLength(featureOffset + 4 + lookupCount * 2);
+ for (tableid_t k = 0; k < lookupCount; k++) {
+ tableid_t lookupid = read_16u(data + featureOffset + 4 + k * 2);
+ if (lookupid < table->lookups.length) {
+ otl_Lookup *lookup = table->lookups.items[lookupid];
+ if (!lookup->name) {
+ if (options->glyph_name_prefix) {
+ lookup->name = sdscatprintf(sdsempty(), "lookup_%s_%c%c%c%c_%d",
+ options->glyph_name_prefix,
+ (tag >> 24) & 0xFF, (tag >> 16) & 0xFF,
+ (tag >> 8) & 0xff, tag & 0xff, lnk++);
+ } else {
+ lookup->name = sdscatprintf(sdsempty(), "lookup_%c%c%c%c_%d",
+ (tag >> 24) & 0xFF, (tag >> 16) & 0xFF,
+ (tag >> 8) & 0xff, tag & 0xff, lnk++);
+ }
+ }
+ otl_iLookupRefList.push(&feature->lookups, lookup);
+ }
+ }
+ otl_iFeatureList.push(&table->features, feature);
+ }
+ }
+
+ // parse script list
+ {
+ tableid_t scriptCount = read_16u(data + scriptListOffset);
+ checkLength(scriptListOffset + 2 + 6 * scriptCount);
+
+ uint32_t nLanguageCombinations = 0;
+ for (tableid_t j = 0; j < scriptCount; j++) {
+ uint32_t scriptOffset =
+ scriptListOffset + read_16u(data + scriptListOffset + 2 + 6 * j + 4);
+ checkLength(scriptOffset + 4);
+
+ tableid_t defaultLangSystem = read_16u(data + scriptOffset);
+ nLanguageCombinations +=
+ (defaultLangSystem ? 1 : 0) + read_16u(data + scriptOffset + 2);
+ }
+
+ for (tableid_t j = 0; j < scriptCount; j++) {
+ uint32_t tag = read_32u(data + scriptListOffset + 2 + 6 * j);
+ uint32_t scriptOffset =
+ scriptListOffset + read_16u(data + scriptListOffset + 2 + 6 * j + 4);
+ tableid_t defaultLangSystem = read_16u(data + scriptOffset);
+ if (defaultLangSystem) {
+ otl_LanguageSystem *lang;
+ otl_iLanguageSystem.init(&lang);
+ lang->name = sdscatprintf(sdsempty(), "%c%c%c%c%cDFLT", (tag >> 24) & 0xFF,
+ (tag >> 16) & 0xFF, (tag >> 8) & 0xff, tag & 0xff,
+ SCRIPT_LANGUAGE_SEPARATOR);
+ parseLanguage(data, tableLength, scriptOffset + defaultLangSystem, lang,
+ &table->features);
+ otl_iLangSystemList.push(&table->languages, lang);
+ }
+ tableid_t langSysCount = read_16u(data + scriptOffset + 2);
+ for (tableid_t k = 0; k < langSysCount; k++) {
+ uint32_t langTag = read_32u(data + scriptOffset + 4 + 6 * k);
+ tableid_t langSys = read_16u(data + scriptOffset + 4 + 6 * k + 4);
+ otl_LanguageSystem *lang;
+ otl_iLanguageSystem.init(&lang);
+ lang->name =
+ sdscatprintf(sdsempty(), "%c%c%c%c%c%c%c%c%c", (tag >> 24) & 0xFF,
+ (tag >> 16) & 0xFF, (tag >> 8) & 0xff, tag & 0xff,
+ SCRIPT_LANGUAGE_SEPARATOR, (langTag >> 24) & 0xFF,
+ (langTag >> 16) & 0xFF, (langTag >> 8) & 0xff, langTag & 0xff);
+ parseLanguage(data, tableLength, scriptOffset + langSys, lang, &table->features);
+ otl_iLangSystemList.push(&table->languages, lang);
+ }
+ }
+ }
+
+ // name all lookups
+ for (tableid_t j = 0; j < table->lookups.length; j++) {
+ if (!table->lookups.items[j]->name) {
+ if (options->glyph_name_prefix) {
+ table->lookups.items[j]->name =
+ sdscatprintf(sdsempty(), "lookup_%s_%02x_%d", options->glyph_name_prefix,
+ table->lookups.items[j]->type, j);
+ } else {
+ table->lookups.items[j]->name =
+ sdscatprintf(sdsempty(), "lookup_%02x_%d", table->lookups.items[j]->type, j);
+ }
+ }
+ }
+ return table;
+FAIL:
+ if (table) table_iOTL.free(table);
+ return NULL;
+}
+
+static void otfcc_readOtl_lookup(font_file_pointer data, uint32_t tableLength, otl_Lookup *lookup,
+ glyphid_t maxGlyphs, const otfcc_Options *options) {
+ lookup->flags = read_16u(data + lookup->_offset + 2);
+ tableid_t subtableCount = read_16u(data + lookup->_offset + 4);
+ if (!subtableCount || tableLength < lookup->_offset + 6 + 2 * subtableCount) {
+ lookup->type = otl_type_unknown;
+ return;
+ }
+ for (tableid_t j = 0; j < subtableCount; j++) {
+ uint32_t subtableOffset = lookup->_offset + read_16u(data + lookup->_offset + 6 + j * 2);
+ otl_Subtable *subtable = otfcc_readOtl_subtable(data, tableLength, subtableOffset,
+ lookup->type, maxGlyphs, options);
+ otl_iSubtableList.push(&lookup->subtables, subtable);
+ }
+ if (lookup->type == otl_type_gsub_extend || lookup->type == otl_type_gpos_extend) {
+ lookup->type = 0;
+ for (tableid_t j = 0; j < lookup->subtables.length; j++) {
+ if (lookup->subtables.items[j]) {
+ lookup->type = lookup->subtables.items[j]->extend.type;
+ break;
+ }
+ }
+ if (lookup->type) {
+ for (tableid_t j = 0; j < lookup->subtables.length; j++) {
+ if (lookup->subtables.items[j] &&
+ lookup->subtables.items[j]->extend.type == lookup->type) {
+ // this subtable is valid
+ otl_Subtable *st = lookup->subtables.items[j]->extend.subtable;
+ FREE(lookup->subtables.items[j]);
+ lookup->subtables.items[j] = st;
+ } else if (lookup->subtables.items[j]) {
+ // type mismatch, delete this subtable
+ otl_Lookup *temp;
+ otl_iLookupPtr.init(&temp);
+ temp->type = lookup->subtables.items[j]->extend.type;
+ otl_iSubtableList.push(&temp->subtables,
+ lookup->subtables.items[j]->extend.subtable);
+ DELETE(otfcc_delete_lookup, temp);
+ FREE(lookup->subtables.items[j]);
+ }
+ }
+ } else {
+ otl_iSubtableList.disposeDependent(&lookup->subtables, lookup);
+ return;
+ }
+ }
+ if (lookup->type == otl_type_gsub_context) lookup->type = otl_type_gsub_chaining;
+ if (lookup->type == otl_type_gpos_context) lookup->type = otl_type_gpos_chaining;
+}
+
+table_OTL *otfcc_readOtl(otfcc_Packet packet, const otfcc_Options *options, uint32_t tag,
+ glyphid_t maxGlyphs) {
+ table_OTL *otl = NULL;
+ FOR_TABLE(tag, table) {
+ font_file_pointer data = table.data;
+ uint32_t length = table.length;
+ otl = otfcc_readOtl_common(data, length,
+ (tag == OTFCC_CHR('G','S','U','B')
+ ? otl_type_gsub_unknown
+ : tag == OTFCC_CHR('G','P','O','S') ? otl_type_gpos_unknown : otl_type_unknown),
+ options);
+ if (!otl) goto FAIL;
+ for (tableid_t j = 0; j < otl->lookups.length; j++) {
+ otfcc_readOtl_lookup(data, length, otl->lookups.items[j], maxGlyphs, options);
+ }
+ return otl;
+ FAIL:
+ if (otl) table_iOTL.free(otl);
+ otl = NULL;
+ }
+ return NULL;
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining.h
new file mode 100644
index 00000000000..a7b14ac8b06
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining.h
@@ -0,0 +1,28 @@
+#ifndef CARYLL_TABLE_OTL_CHAINING_H
+#define CARYLL_TABLE_OTL_CHAINING_H
+
+#include "common.h"
+
+// This file supports four formats
+// GSUB contextual
+// GSUB chaining
+// GPOS contextual
+// GPOS chaining
+
+otl_Subtable *otl_read_chaining(const font_file_pointer data, uint32_t tableLength, uint32_t offset,
+ const glyphid_t maxGlyphs, const otfcc_Options *options);
+otl_Subtable *otl_read_contextual(const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options);
+json_value *otl_dump_chaining(const otl_Subtable *_subtable);
+otl_Subtable *otl_parse_chaining(const json_value *_subtable, const otfcc_Options *options);
+caryll_Buffer *otfcc_build_chaining(const otl_Subtable *_subtable);
+caryll_Buffer *otfcc_build_contextual(const otl_Subtable *_subtable);
+
+tableid_t otfcc_classifiedBuildChaining(const otl_Lookup *lookup,
+ OUT caryll_Buffer ***subtableBuffers,
+ MODIFY size_t *lastOffset);
+
+bool otfcc_chainingLookupIsContextualLookup(const otl_Lookup *lookup);
+
+#endif
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/build.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/build.c
new file mode 100644
index 00000000000..6ec7958addb
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/build.c
@@ -0,0 +1,239 @@
+#include "../chaining.h"
+#include "common.h"
+
+bool otfcc_chainingLookupIsContextualLookup(const otl_Lookup *lookup) {
+ if (!(lookup->type == otl_type_gpos_chaining || lookup->type == otl_type_gsub_chaining))
+ return false;
+ bool isContextual = true;
+ for (tableid_t j = 0; j < lookup->subtables.length; j++) {
+ const subtable_chaining *subtable = &(lookup->subtables.items[j]->chaining);
+ if (subtable->type == otl_chaining_classified) {
+ for (tableid_t k = 0; k < subtable->rulesCount; k++) {
+ otl_ChainingRule *rule = subtable->rules[k];
+ tableid_t nBacktrack = rule->inputBegins;
+ tableid_t nLookahead = rule->matchCount - rule->inputEnds;
+ isContextual = isContextual && !nBacktrack && !nLookahead;
+ }
+ } else {
+ const otl_ChainingRule *rule = (const otl_ChainingRule *)&(subtable->rule);
+ tableid_t nBacktrack = rule->inputBegins;
+ tableid_t nLookahead = rule->matchCount - rule->inputEnds;
+ isContextual = isContextual && !nBacktrack && !nLookahead;
+ }
+ }
+ return isContextual;
+}
+
+////// CHAINING
+
+static caryll_Buffer *otfcc_build_chaining_coverage(const otl_Subtable *_subtable) {
+ const subtable_chaining *subtable = &(_subtable->chaining);
+ const otl_ChainingRule *rule = (const otl_ChainingRule *)&(subtable->rule);
+ tableid_t nBacktrack = rule->inputBegins;
+ tableid_t nInput = rule->inputEnds - rule->inputBegins;
+ tableid_t nLookahead = rule->matchCount - rule->inputEnds;
+ tableid_t nSubst = rule->applyCount;
+ reverseBacktracks(rule);
+
+ bk_Block *root = bk_new_Block(b16, 3, // format
+ bkover);
+
+ bk_push(root, b16, nBacktrack, bkover);
+ for (tableid_t j = 0; j < rule->inputBegins; j++) {
+ bk_push(root, p16, bk_newBlockFromBuffer(Coverage.build(rule->match[j])), bkover);
+ }
+ bk_push(root, b16, nInput, bkover);
+ for (tableid_t j = rule->inputBegins; j < rule->inputEnds; j++) {
+ bk_push(root, p16, bk_newBlockFromBuffer(Coverage.build(rule->match[j])), bkover);
+ }
+ bk_push(root, b16, nLookahead, bkover);
+ for (tableid_t j = rule->inputEnds; j < rule->matchCount; j++) {
+ bk_push(root, p16, bk_newBlockFromBuffer(Coverage.build(rule->match[j])), bkover);
+ }
+ bk_push(root, b16, rule->applyCount, bkover);
+ for (tableid_t j = 0; j < nSubst; j++) {
+ bk_push(root, b16, rule->apply[j].index - nBacktrack, // position
+ b16, rule->apply[j].lookup.index, // lookup
+ bkover);
+ }
+
+ return bk_build_Block(root);
+}
+
+static caryll_Buffer *otfcc_build_chaining_classes(const otl_Subtable *_subtable) {
+ const subtable_chaining *subtable = &(_subtable->chaining);
+
+ otl_Coverage *coverage;
+ NEW(coverage);
+ coverage->numGlyphs = subtable->ic->numGlyphs;
+ coverage->glyphs = subtable->ic->glyphs;
+
+ bk_Block *root =
+ bk_new_Block(b16, 2, // format
+ p16, bk_newBlockFromBuffer(Coverage.build(coverage)), // coverage
+ p16, bk_newBlockFromBuffer(ClassDef.build(subtable->bc)), // BacktrackClassDef
+ p16, bk_newBlockFromBuffer(ClassDef.build(subtable->ic)), // InputClassDef
+ p16, bk_newBlockFromBuffer(ClassDef.build(subtable->fc)), // LookaheadClassDef
+ b16, subtable->ic->maxclass + 1, // ChainSubClassSetCnt
+ bkover);
+
+ glyphclass_t *rcpg;
+ NEW(rcpg, subtable->ic->maxclass + 1);
+ for (glyphclass_t j = 0; j <= subtable->ic->maxclass; j++) {
+ rcpg[j] = 0;
+ }
+ for (tableid_t j = 0; j < subtable->rulesCount; j++) {
+ tableid_t ib = subtable->rules[j]->inputBegins;
+ tableid_t startClass = subtable->rules[j]->match[ib]->glyphs[0].index;
+ if (startClass <= subtable->ic->maxclass) rcpg[startClass] += 1;
+ }
+
+ for (glyphclass_t j = 0; j <= subtable->ic->maxclass; j++) {
+ if (rcpg[j]) {
+ bk_Block *cset = bk_new_Block(b16, rcpg[j], // ChainSubClassRuleCnt
+ bkover);
+ for (tableid_t k = 0; k < subtable->rulesCount; k++) {
+ otl_ChainingRule *rule = subtable->rules[k];
+ glyphclass_t startClass = rule->match[rule->inputBegins]->glyphs[0].index;
+ if (startClass != j) { continue; }
+ reverseBacktracks(rule);
+ tableid_t nBacktrack = rule->inputBegins;
+ tableid_t nInput = rule->inputEnds - rule->inputBegins;
+ tableid_t nLookahead = rule->matchCount - rule->inputEnds;
+ tableid_t nSubst = rule->applyCount;
+ bk_Block *r = bk_new_Block(bkover);
+ bk_push(r, b16, nBacktrack, bkover);
+ for (tableid_t m = 0; m < rule->inputBegins; m++) {
+ bk_push(r, b16, rule->match[m]->glyphs[0].index, bkover);
+ }
+ bk_push(r, b16, nInput, bkover);
+ for (tableid_t m = rule->inputBegins + 1; m < rule->inputEnds; m++) {
+ bk_push(r, b16, rule->match[m]->glyphs[0].index, bkover);
+ }
+ bk_push(r, b16, nLookahead, bkover);
+ for (tableid_t m = rule->inputEnds; m < rule->matchCount; m++) {
+ bk_push(r, b16, rule->match[m]->glyphs[0].index, bkover);
+ }
+ bk_push(r, b16, nSubst, bkover);
+ for (tableid_t m = 0; m < nSubst; m++) {
+ bk_push(r, b16, rule->apply[m].index - nBacktrack, // position
+ b16, rule->apply[m].lookup.index, // lookup index
+ bkover);
+ }
+ bk_push(cset, p16, r, bkover);
+ }
+ bk_push(root, p16, cset, bkover);
+ } else {
+ bk_push(root, p16, NULL, bkover);
+ }
+ }
+
+ FREE(coverage);
+ FREE(rcpg);
+ return bk_build_Block(root);
+}
+
+caryll_Buffer *otfcc_build_chaining(const otl_Subtable *_subtable) {
+ if (_subtable->chaining.type == otl_chaining_classified) {
+ return otfcc_build_chaining_classes(_subtable);
+ } else {
+ return otfcc_build_chaining_coverage(_subtable);
+ }
+}
+
+////// CONTEXTUAL
+
+static caryll_Buffer *otfcc_build_contextual_coverage(const otl_Subtable *_subtable) {
+ const subtable_chaining *subtable = &(_subtable->chaining);
+ const otl_ChainingRule *rule = (const otl_ChainingRule *)&(subtable->rule);
+ tableid_t nInput = rule->inputEnds - rule->inputBegins;
+ tableid_t nSubst = rule->applyCount;
+ reverseBacktracks(rule);
+
+ bk_Block *root = bk_new_Block(b16, 3, // format
+ bkover);
+
+ bk_push(root, b16, nInput, bkover);
+ bk_push(root, b16, nSubst, bkover);
+ for (tableid_t j = rule->inputBegins; j < rule->inputEnds; j++) {
+ bk_push(root, p16, bk_newBlockFromBuffer(Coverage.build(rule->match[j])), bkover);
+ }
+ for (tableid_t j = 0; j < nSubst; j++) {
+ bk_push(root, b16, rule->apply[j].index, // position
+ b16, rule->apply[j].lookup.index, // lookup
+ bkover);
+ }
+
+ return bk_build_Block(root);
+}
+
+static caryll_Buffer *otfcc_build_contextual_classes(const otl_Subtable *_subtable) {
+ const subtable_chaining *subtable = &(_subtable->chaining);
+
+ otl_Coverage *coverage;
+ NEW(coverage);
+ coverage->numGlyphs = subtable->ic->numGlyphs;
+ coverage->glyphs = subtable->ic->glyphs;
+
+ bk_Block *root =
+ bk_new_Block(b16, 2, // format
+ p16, bk_newBlockFromBuffer(Coverage.build(coverage)), // coverage
+ p16, bk_newBlockFromBuffer(ClassDef.build(subtable->ic)), // InputClassDef
+ b16, subtable->ic->maxclass + 1, // ChainSubClassSetCnt
+ bkover);
+
+ glyphclass_t *rcpg;
+ NEW(rcpg, subtable->ic->maxclass + 1);
+ for (glyphclass_t j = 0; j <= subtable->ic->maxclass; j++) {
+ rcpg[j] = 0;
+ }
+ for (tableid_t j = 0; j < subtable->rulesCount; j++) {
+ tableid_t ib = subtable->rules[j]->inputBegins;
+ tableid_t startClass = subtable->rules[j]->match[ib]->glyphs[0].index;
+ if (startClass <= subtable->ic->maxclass) rcpg[startClass] += 1;
+ }
+
+ for (glyphclass_t j = 0; j <= subtable->ic->maxclass; j++) {
+ if (rcpg[j]) {
+ bk_Block *cset = bk_new_Block(b16, rcpg[j], // ChainSubClassRuleCnt
+ bkover);
+ for (tableid_t k = 0; k < subtable->rulesCount; k++) {
+ otl_ChainingRule *rule = subtable->rules[k];
+ glyphclass_t startClass = rule->match[rule->inputBegins]->glyphs[0].index;
+ if (startClass != j) { continue; }
+ reverseBacktracks(rule);
+ tableid_t nInput = rule->inputEnds - rule->inputBegins;
+ tableid_t nSubst = rule->applyCount;
+ bk_Block *r = bk_new_Block(bkover);
+
+ bk_push(r, b16, nInput, bkover);
+ bk_push(r, b16, nSubst, bkover);
+ for (tableid_t m = rule->inputBegins + 1; m < rule->inputEnds; m++) {
+ bk_push(r, b16, rule->match[m]->glyphs[0].index, bkover);
+ }
+
+ for (tableid_t m = 0; m < nSubst; m++) {
+ bk_push(r, b16, rule->apply[m].index, // position
+ b16, rule->apply[m].lookup.index, // lookup index
+ bkover);
+ }
+ bk_push(cset, p16, r, bkover);
+ }
+ bk_push(root, p16, cset, bkover);
+ } else {
+ bk_push(root, p16, NULL, bkover);
+ }
+ }
+
+ FREE(coverage);
+ FREE(rcpg);
+ return bk_build_Block(root);
+}
+
+caryll_Buffer *otfcc_build_contextual(const otl_Subtable *_subtable) {
+ if (_subtable->chaining.type == otl_chaining_classified) {
+ return otfcc_build_contextual_classes(_subtable);
+ } else {
+ return otfcc_build_contextual_coverage(_subtable);
+ }
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/classifier.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/classifier.c
new file mode 100644
index 00000000000..a5c904b693e
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/classifier.c
@@ -0,0 +1,238 @@
+#include "../chaining.h"
+
+// Chaining substitution classifier
+// We will merge similar subtables.
+
+typedef struct {
+ int gid;
+ sds gname;
+ int cls;
+ UT_hash_handle hh;
+} classifier_hash;
+static int by_gid_clsh(classifier_hash *a, classifier_hash *b) {
+ return a->gid - b->gid;
+}
+
+static int classCompatible(classifier_hash **h, otl_Coverage *cov, int *past) {
+ // checks whether a coverage is compatible to a class hash.
+ classifier_hash *s = NULL;
+ if (cov->numGlyphs == 0) return 1;
+ int gid = cov->glyphs[0].index;
+ // check pass
+ HASH_FIND_INT(*h, &gid, s);
+ if (s) {
+ // the coverage has been defined into a class
+ classifier_hash *ss, *tmp;
+ for (glyphid_t j = 1; j < cov->numGlyphs; j++) {
+ int gid = cov->glyphs[j].index;
+ HASH_FIND_INT(*h, &gid, ss);
+ if (!ss || ss->cls != s->cls) return 0;
+ }
+ // reverse check: all glyphs classified are there in the coverage
+ classifier_hash *revh = NULL;
+ for (glyphid_t j = 0; j < cov->numGlyphs; j++) {
+ int gid = cov->glyphs[j].index;
+ classifier_hash *rss = NULL;
+ HASH_FIND_INT(revh, &gid, rss);
+ if (!rss) {
+ NEW(rss);
+ rss->gid = gid;
+ rss->gname = cov->glyphs[j].name;
+ rss->cls = s->cls;
+ HASH_ADD_INT(revh, gid, rss);
+ }
+ }
+
+ bool allcheck = true;
+ foreach_hash(ss, *h) if (ss->cls == s->cls) {
+ int gid = ss->gid;
+ classifier_hash *rss;
+ HASH_FIND_INT(revh, &gid, rss);
+ if (!rss) {
+ allcheck = false;
+ break;
+ }
+ }
+ HASH_ITER(hh, revh, ss, tmp) {
+ HASH_DEL(revh, ss);
+ FREE(ss);
+ }
+ return allcheck ? s->cls : 0;
+ } else {
+ // the coverage is not defined into a class.
+ classifier_hash *ss;
+ for (glyphid_t j = 1; j < cov->numGlyphs; j++) {
+ int gid = cov->glyphs[j].index;
+ HASH_FIND_INT(*h, &gid, ss);
+ if (ss) return 0;
+ }
+ for (glyphid_t j = 0; j < cov->numGlyphs; j++) {
+ int gid = cov->glyphs[j].index;
+ classifier_hash *s = NULL;
+ HASH_FIND_INT(*h, &gid, s);
+ if (!s) {
+ NEW(s);
+ s->gid = cov->glyphs[j].index;
+ s->gname = cov->glyphs[j].name;
+ s->cls = *past + 1;
+ HASH_ADD_INT(*h, gid, s);
+ }
+ }
+ *past += 1;
+ return 1;
+ }
+}
+static otl_ChainingRule *buildRule(otl_ChainingRule *rule, classifier_hash *hb, classifier_hash *hi,
+ classifier_hash *hf) {
+ otl_ChainingRule *newRule;
+ NEW(newRule);
+ newRule->matchCount = rule->matchCount;
+ newRule->inputBegins = rule->inputBegins;
+ newRule->inputEnds = rule->inputEnds;
+ NEW(newRule->match, newRule->matchCount);
+ for (tableid_t m = 0; m < rule->matchCount; m++) {
+ NEW(newRule->match[m]);
+ newRule->match[m]->numGlyphs = 1;
+ NEW(newRule->match[m]->glyphs);
+ if (rule->match[m]->numGlyphs > 0) {
+ classifier_hash *h = (m < rule->inputBegins ? hb : m < rule->inputEnds ? hi : hf);
+ classifier_hash *s;
+ int gid = rule->match[m]->glyphs[0].index;
+ HASH_FIND_INT(h, &gid, s);
+ newRule->match[m]->glyphs[0] = Handle.fromIndex(s->cls);
+ } else {
+ newRule->match[m]->glyphs[0] = Handle.fromIndex(0);
+ }
+ }
+ newRule->applyCount = rule->applyCount;
+ NEW(newRule->apply, newRule->applyCount);
+ for (tableid_t j = 0; j < rule->applyCount; j++) {
+ newRule->apply[j].index = rule->apply[j].index;
+ newRule->apply[j].lookup = Handle.dup(rule->apply[j].lookup);
+ }
+ return newRule;
+}
+static otl_ClassDef *toClass(classifier_hash **h) {
+ otl_ClassDef *cd = ClassDef.create();
+ classifier_hash *item;
+ HASH_SORT(*h, by_gid_clsh);
+ foreach_hash(item, *h) {
+ ClassDef.push(cd, Handle.fromConsolidated(item->gid, item->gname), item->cls);
+ }
+ return cd;
+}
+static tableid_t tryClassifyAround(const otl_Lookup *lookup, tableid_t j,
+ OUT subtable_chaining **classifiedST) {
+ tableid_t compatibleCount = 0;
+ classifier_hash *hb = NULL;
+ classifier_hash *hi = NULL;
+ classifier_hash *hf = NULL;
+ // initialize the class hash
+ subtable_chaining *subtable0 = &(lookup->subtables.items[j]->chaining);
+ int classno_b = 0;
+ int classno_i = 0;
+ int classno_f = 0;
+
+ otl_ChainingRule *rule0 = &subtable0->rule;
+ for (tableid_t m = 0; m < rule0->matchCount; m++) {
+ int check = 0;
+ if (m < rule0->inputBegins) {
+ check = classCompatible(&hb, rule0->match[m], &classno_b);
+ } else if (m < rule0->inputEnds) {
+ check = classCompatible(&hi, rule0->match[m], &classno_i);
+ } else {
+ check = classCompatible(&hf, rule0->match[m], &classno_f);
+ }
+ if (!check) { goto FAIL; }
+ }
+ for (tableid_t k = j + 1; k < lookup->subtables.length; k++) {
+ otl_ChainingRule *rule = &lookup->subtables.items[k]->chaining.rule;
+ bool allcheck = true;
+ for (tableid_t m = 0; m < rule->matchCount; m++) {
+ int check = 0;
+ if (m < rule->inputBegins) {
+ check = classCompatible(&hb, rule->match[m], &classno_b);
+ } else if (m < rule->inputEnds) {
+ check = classCompatible(&hi, rule->match[m], &classno_i);
+ } else {
+ check = classCompatible(&hf, rule->match[m], &classno_f);
+ }
+ if (!check) {
+ allcheck = false;
+ goto endcheck;
+ }
+ }
+ if (allcheck) { compatibleCount += 1; }
+ }
+endcheck:
+ if (compatibleCount > 1) {
+ // We've found multiple compatible subtables;
+ NEW(subtable0);
+ subtable0->rulesCount = compatibleCount + 1;
+ NEW(subtable0->rules, compatibleCount + 1);
+
+ subtable0->rules[0] = buildRule(rule0, hb, hi, hf);
+ // write other rules
+ tableid_t kk = 1;
+ for (tableid_t k = j + 1; k < lookup->subtables.length && kk < compatibleCount + 1; k++) {
+ otl_ChainingRule *rule = &lookup->subtables.items[k]->chaining.rule;
+ subtable0->rules[kk] = buildRule(rule, hb, hi, hf);
+ kk++;
+ }
+
+ subtable0->type = otl_chaining_classified;
+ subtable0->bc = toClass(&hb);
+ subtable0->ic = toClass(&hi);
+ subtable0->fc = toClass(&hf);
+ *classifiedST = subtable0;
+ }
+FAIL:;
+ if (hb) {
+ classifier_hash *s, *tmp;
+ HASH_ITER(hh, hb, s, tmp) {
+ HASH_DEL(hb, s);
+ FREE(s);
+ }
+ }
+ if (hi) {
+ classifier_hash *s, *tmp;
+ HASH_ITER(hh, hi, s, tmp) {
+ HASH_DEL(hi, s);
+ FREE(s);
+ }
+ }
+ if (hf) {
+ classifier_hash *s, *tmp;
+ HASH_ITER(hh, hf, s, tmp) {
+ HASH_DEL(hf, s);
+ FREE(s);
+ }
+ }
+
+ if (compatibleCount > 1) {
+ return compatibleCount;
+ } else {
+ return 0;
+ }
+}
+tableid_t otfcc_classifiedBuildChaining(const otl_Lookup *lookup,
+ OUT caryll_Buffer ***subtableBuffers,
+ MODIFY size_t *lastOffset) {
+ bool isContextual = otfcc_chainingLookupIsContextualLookup(lookup);
+ tableid_t subtablesWritten = 0;
+ NEW(*subtableBuffers, lookup->subtables.length);
+ for (tableid_t j = 0; j < lookup->subtables.length; j++) {
+ subtable_chaining *st0 = &(lookup->subtables.items[j]->chaining);
+ if (st0->type) continue;
+ subtable_chaining *st = st0;
+ // Try to classify subtables after j into j
+ j += tryClassifyAround(lookup, j, &st);
+ caryll_Buffer *buf = isContextual ? otfcc_build_contextual((otl_Subtable *)st)
+ : otfcc_build_chaining((otl_Subtable *)st);
+ if (st != st0) { iSubtable_chaining.free(st); }
+ (*subtableBuffers)[subtablesWritten] = buf;
+ *lastOffset += buf->size;
+ subtablesWritten += 1;
+ }
+ return subtablesWritten;
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/common.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/common.c
new file mode 100644
index 00000000000..c670a1dcbca
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/common.c
@@ -0,0 +1,23 @@
+#include "../chaining.h"
+#include "common.h"
+static void otl_init_chaining(subtable_chaining *subtable) {
+ memset(subtable, 0, sizeof(*subtable));
+}
+static void otl_dispose_chaining(subtable_chaining *subtable) {
+ if (subtable->type) {
+ if (subtable->rules) {
+ for (tableid_t j = 0; j < subtable->rulesCount; j++) {
+ deleteRule(subtable->rules[j]);
+ }
+ FREE(subtable->rules);
+ }
+ if (subtable->bc) { ClassDef.free(subtable->bc); }
+ if (subtable->ic) { ClassDef.free(subtable->ic); }
+ if (subtable->fc) { ClassDef.free(subtable->fc); }
+ } else {
+ closeRule(&subtable->rule);
+ }
+}
+
+caryll_standardRefType(subtable_chaining, iSubtable_chaining, otl_init_chaining,
+ otl_dispose_chaining);
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/common.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/common.h
new file mode 100644
index 00000000000..db8cf92898e
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/common.h
@@ -0,0 +1,33 @@
+#include "../chaining.h"
+
+static INLINE void closeRule(otl_ChainingRule *rule) {
+ if (rule && rule->match && rule->matchCount) {
+ for (tableid_t k = 0; k < rule->matchCount; k++) {
+ Coverage.free(rule->match[k]);
+ }
+ FREE(rule->match);
+ }
+ if (rule && rule->apply) {
+ for (tableid_t j = 0; j < rule->applyCount; j++) {
+ Handle.dispose(&rule->apply[j].lookup);
+ }
+ FREE(rule->apply);
+ }
+}
+static INLINE void deleteRule(otl_ChainingRule *rule) {
+ if (!rule) return;
+ closeRule(rule);
+ FREE(rule);
+}
+static INLINE void reverseBacktracks(otl_ChainingRule *rule) {
+ if (rule->inputBegins > 0) {
+ tableid_t start = 0;
+ tableid_t end = rule->inputBegins - 1;
+ while (end > start) {
+ otl_Coverage *tmp = rule->match[start];
+ rule->match[start] = rule->match[end];
+ rule->match[end] = tmp;
+ end--, start++;
+ }
+ }
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/dump.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/dump.c
new file mode 100644
index 00000000000..31332ca8a5e
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/dump.c
@@ -0,0 +1,27 @@
+#include "../chaining.h"
+
+json_value *otl_dump_chaining(const otl_Subtable *_subtable) {
+ const subtable_chaining *subtable = &(_subtable->chaining);
+ if (subtable->type) return json_null_new();
+ const otl_ChainingRule *rule = &(subtable->rule);
+ json_value *_st = json_object_new(4);
+
+ json_value *_match = json_array_new(rule->matchCount);
+ for (tableid_t j = 0; j < rule->matchCount; j++) {
+ json_array_push(_match, Coverage.dump(rule->match[j]));
+ }
+ json_object_push(_st, "match", _match);
+
+ json_value *_apply = json_array_new(rule->applyCount);
+ for (tableid_t j = 0; j < rule->applyCount; j++) {
+ json_value *_application = json_object_new(2);
+ json_object_push(_application, "at", json_integer_new(rule->apply[j].index));
+ json_object_push(_application, "lookup", json_string_new(rule->apply[j].lookup.name));
+ json_array_push(_apply, _application);
+ }
+ json_object_push(_st, "apply", preserialize(_apply));
+
+ json_object_push(_st, "inputBegins", json_integer_new(rule->inputBegins));
+ json_object_push(_st, "inputEnds", json_integer_new(rule->inputEnds));
+ return _st;
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/parse.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/parse.c
new file mode 100644
index 00000000000..7a2c1e2d24b
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/parse.c
@@ -0,0 +1,37 @@
+#include "../chaining.h"
+
+otl_Subtable *otl_parse_chaining(const json_value *_subtable, const otfcc_Options *options) {
+ json_value *_match = json_obj_get_type(_subtable, "match", json_array);
+ json_value *_apply = json_obj_get_type(_subtable, "apply", json_array);
+ if (!_match || !_apply) return NULL;
+
+ subtable_chaining *subtable = iSubtable_chaining.create();
+ subtable->type = otl_chaining_canonical;
+
+ otl_ChainingRule *rule = &subtable->rule;
+
+ rule->matchCount = _match->u.array.length;
+ NEW(rule->match, rule->matchCount);
+ rule->applyCount = _apply->u.array.length;
+ NEW(rule->apply, rule->applyCount);
+
+ rule->inputBegins = json_obj_getnum_fallback(_subtable, "inputBegins", 0);
+ rule->inputEnds = json_obj_getnum_fallback(_subtable, "inputEnds", rule->matchCount);
+
+ for (tableid_t j = 0; j < rule->matchCount; j++) {
+ rule->match[j] = Coverage.parse(_match->u.array.values[j]);
+ }
+ for (tableid_t j = 0; j < rule->applyCount; j++) {
+ rule->apply[j].index = 0;
+ rule->apply[j].lookup = Handle.empty();
+ json_value *_application = _apply->u.array.values[j];
+ if (_application->type == json_object) {
+ json_value *_ln = json_obj_get_type(_application, "lookup", json_string);
+ if (_ln) {
+ rule->apply[j].lookup = Handle.fromName(sdsnewlen(_ln->u.string.ptr, _ln->u.string.length));
+ rule->apply[j].index = json_obj_getnum(_application, "at");
+ }
+ }
+ }
+ return (otl_Subtable *)subtable;
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/read.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/read.c
new file mode 100644
index 00000000000..0059efeaec9
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/chaining/read.c
@@ -0,0 +1,416 @@
+#include "../chaining.h"
+#include "common.h"
+
+typedef struct {
+ otl_ClassDef *bc;
+ otl_ClassDef *ic;
+ otl_ClassDef *fc;
+} classdefs;
+
+static otl_Coverage *singleCoverage(font_file_pointer data, uint32_t tableLength, uint16_t gid,
+ uint32_t _offset, uint16_t kind, const glyphid_t maxGlyphs,
+ void *userdata) {
+ otl_Coverage *cov;
+ NEW(cov);
+ cov->numGlyphs = 1;
+ NEW(cov->glyphs);
+ cov->glyphs[0] = Handle.fromIndex((glyphid_t)gid);
+ return cov;
+}
+static otl_Coverage *classCoverage(font_file_pointer data, uint32_t tableLength, uint16_t cls,
+ uint32_t _offset, uint16_t kind, const glyphid_t maxGlyphs,
+ void *_classdefs) {
+ classdefs *defs = (classdefs *)_classdefs;
+ otl_ClassDef *cd = (kind == 1 ? defs->bc : kind == 2 ? defs->ic : defs->fc);
+ otl_Coverage *cov;
+ NEW(cov);
+ cov->numGlyphs = 0;
+ cov->glyphs = NULL;
+ glyphid_t count = 0;
+ if (cls == 0) {
+ for (glyphid_t k = 0; k < maxGlyphs; k++) {
+ bool found = false;
+ for (glyphid_t j = 0; j < cd->numGlyphs; j++) {
+ if (cd->classes[j] > 0 && cd->glyphs[j].index == k) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) count += 1;
+ }
+ } else {
+ for (glyphid_t j = 0; j < cd->numGlyphs; j++) {
+ if (cd->classes[j] == cls) count++;
+ }
+ }
+ if (!count) return cov;
+
+ cov->numGlyphs = count;
+ NEW(cov->glyphs, count);
+ glyphid_t jj = 0;
+ if (cls == 0) {
+ for (glyphid_t k = 0; k < maxGlyphs; k++) {
+ bool found = false;
+ for (glyphid_t j = 0; j < cd->numGlyphs; j++) {
+ if (cd->classes[j] > 0 && cd->glyphs[j].index == k) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) cov->glyphs[jj++] = Handle.fromIndex(k);
+ }
+ } else {
+ for (glyphid_t j = 0; j < cd->numGlyphs; j++) {
+ if (cd->classes[j] == cls) { cov->glyphs[jj++] = Handle.dup(cd->glyphs[j]); }
+ }
+ }
+ return cov;
+}
+static otl_Coverage *format3Coverage(font_file_pointer data, uint32_t tableLength, uint16_t shift,
+ uint32_t _offset, uint16_t kind, const glyphid_t maxGlyphs,
+ void *userdata) {
+ return Coverage.read(data, tableLength, _offset + shift - 2);
+}
+
+typedef otl_Coverage *(*CoverageReaderHandler)(font_file_pointer, uint32_t, uint16_t, uint32_t,
+ uint16_t, glyphid_t, void *);
+
+static otl_ChainingRule *GeneralReadContextualRule(font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, uint16_t startGID, bool minusOne,
+ CoverageReaderHandler fn, const glyphid_t maxGlyphs,
+ void *userdata) {
+ otl_ChainingRule *rule;
+ NEW(rule);
+ rule->match = NULL;
+ rule->apply = NULL;
+
+ uint16_t minusOneQ = (minusOne ? 1 : 0);
+
+ checkLength(offset + 4);
+ uint16_t nInput = read_16u(data + offset);
+ uint16_t nApply = read_16u(data + offset + 2);
+ checkLength(offset + 4 + 2 * nInput + 4 * nApply);
+
+ rule->matchCount = nInput;
+ rule->inputBegins = 0;
+ rule->inputEnds = nInput;
+
+ NEW(rule->match, rule->matchCount);
+ uint16_t jj = 0;
+ if (minusOne) {
+ rule->match[jj++] = fn(data, tableLength, startGID, offset, 2, maxGlyphs, userdata);
+ }
+ for (uint16_t j = 0; j < nInput - minusOneQ; j++) {
+ uint32_t gid = read_16u(data + offset + 4 + j * 2);
+ rule->match[jj++] = fn(data, tableLength, gid, offset, 2, maxGlyphs, userdata);
+ }
+ rule->applyCount = nApply;
+ NEW(rule->apply, rule->applyCount);
+ for (tableid_t j = 0; j < nApply; j++) {
+ rule->apply[j].index =
+ rule->inputBegins +
+ read_16u(data + offset + 4 + 2 * (rule->matchCount - minusOneQ) + j * 4);
+ rule->apply[j].lookup = Handle.fromIndex(
+ read_16u(data + offset + 4 + 2 * (rule->matchCount - minusOneQ) + j * 4 + 2));
+ }
+ reverseBacktracks(rule);
+ return rule;
+
+FAIL:
+ DELETE(deleteRule, rule);
+ return NULL;
+}
+static subtable_chaining *readContextualFormat1(subtable_chaining *subtable,
+ const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs) {
+ // Contextual Substitution Subtable, Simple.
+ checkLength(offset + 6);
+
+ uint16_t covOffset = offset + read_16u(data + offset + 2);
+ otl_Coverage *firstCoverage = Coverage.read(data, tableLength, covOffset);
+
+ tableid_t chainSubRuleSetCount = read_16u(data + offset + 4);
+ if (chainSubRuleSetCount != firstCoverage->numGlyphs) goto FAIL;
+ checkLength(offset + 6 + 2 * chainSubRuleSetCount);
+
+ tableid_t totalRules = 0;
+ for (tableid_t j = 0; j < chainSubRuleSetCount; j++) {
+ uint32_t srsOffset = offset + read_16u(data + offset + 6 + j * 2);
+ checkLength(srsOffset + 2);
+ totalRules += read_16u(data + srsOffset);
+ checkLength(srsOffset + 2 + 2 * read_16u(data + srsOffset));
+ }
+ subtable->rulesCount = totalRules;
+ NEW(subtable->rules, totalRules);
+
+ tableid_t jj = 0;
+ for (tableid_t j = 0; j < chainSubRuleSetCount; j++) {
+ uint32_t srsOffset = offset + read_16u(data + offset + 6 + j * 2);
+ tableid_t srsCount = read_16u(data + srsOffset);
+ for (tableid_t k = 0; k < srsCount; k++) {
+ uint32_t srOffset = srsOffset + read_16u(data + srsOffset + 2 + k * 2);
+ subtable->rules[jj] = GeneralReadContextualRule(data, tableLength, srOffset,
+ firstCoverage->glyphs[j].index, true,
+ singleCoverage, maxGlyphs, NULL);
+ jj += 1;
+ }
+ }
+
+ Coverage.free(firstCoverage);
+ return subtable;
+FAIL:
+ iSubtable_chaining.free(subtable);
+ return NULL;
+}
+static subtable_chaining *readContextualFormat2(subtable_chaining *subtable,
+ const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs) {
+ // Contextual Substitution Subtable, Class based.
+ checkLength(offset + 8);
+
+ classdefs *cds;
+ NEW(cds);
+ cds->bc = NULL;
+ cds->ic = ClassDef.read(data, tableLength, offset + read_16u(data + offset + 4));
+ cds->fc = NULL;
+
+ tableid_t chainSubClassSetCnt = read_16u(data + offset + 6);
+ checkLength(offset + 12 + 2 * chainSubClassSetCnt);
+
+ tableid_t totalRules = 0;
+ for (tableid_t j = 0; j < chainSubClassSetCnt; j++) {
+ uint32_t srcOffset = read_16u(data + offset + 8 + j * 2);
+ if (srcOffset) { totalRules += read_16u(data + offset + srcOffset); }
+ }
+ subtable->rulesCount = totalRules;
+ NEW(subtable->rules, totalRules);
+
+ tableid_t jj = 0;
+ for (tableid_t j = 0; j < chainSubClassSetCnt; j++) {
+ uint32_t srcOffset = read_16u(data + offset + 8 + j * 2);
+ if (srcOffset) {
+ tableid_t srsCount = read_16u(data + offset + srcOffset);
+ for (tableid_t k = 0; k < srsCount; k++) {
+ uint32_t srOffset =
+ offset + srcOffset + read_16u(data + offset + srcOffset + 2 + k * 2);
+ subtable->rules[jj] = GeneralReadContextualRule(
+ data, tableLength, srOffset, j, true, classCoverage, maxGlyphs, cds);
+ jj += 1;
+ }
+ }
+ }
+ if (cds) {
+ if (cds->bc) ClassDef.free(cds->bc);
+ if (cds->ic) ClassDef.free(cds->ic);
+ if (cds->fc) ClassDef.free(cds->fc);
+ FREE(cds);
+ }
+ return subtable;
+FAIL:
+ iSubtable_chaining.free(subtable);
+ return NULL;
+}
+otl_Subtable *otl_read_contextual(const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options) {
+ uint16_t format = 0;
+ subtable_chaining *subtable = iSubtable_chaining.create();
+ subtable->type = otl_chaining_poly;
+
+ checkLength(offset + 2);
+ format = read_16u(data + offset);
+ if (format == 1) {
+ return (otl_Subtable *)readContextualFormat1(subtable, data, tableLength, offset,
+ maxGlyphs);
+ } else if (format == 2) {
+ return (otl_Subtable *)readContextualFormat2(subtable, data, tableLength, offset,
+ maxGlyphs);
+ } else if (format == 3) {
+ // Contextual Substitution Subtable, Coverage based.
+ subtable->rulesCount = 1;
+ NEW(subtable->rules, 1);
+ subtable->rules[0] = GeneralReadContextualRule(data, tableLength, offset + 2, 0, false,
+ format3Coverage, maxGlyphs, NULL);
+ return (otl_Subtable *)subtable;
+ }
+FAIL:
+ logWarning("Unsupported format %d.\n", format);
+ iSubtable_chaining.free(subtable);
+ return NULL;
+}
+
+static otl_ChainingRule *GeneralReadChainingRule(font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, uint16_t startGID, bool minusOne,
+ CoverageReaderHandler fn, const glyphid_t maxGlyphs,
+ void *userdata) {
+ otl_ChainingRule *rule;
+ NEW(rule);
+ rule->match = NULL;
+ rule->apply = NULL;
+
+ uint16_t minusOneQ = (minusOne ? 1 : 0);
+
+ checkLength(offset + 8);
+ tableid_t nBack = read_16u(data + offset);
+ checkLength(offset + 2 + 2 * nBack + 2);
+ tableid_t nInput = read_16u(data + offset + 2 + 2 * nBack);
+ checkLength(offset + 4 + 2 * (nBack + nInput - minusOneQ) + 2);
+ tableid_t nLookaround = read_16u(data + offset + 4 + 2 * (nBack + nInput - minusOneQ));
+ checkLength(offset + 6 + 2 * (nBack + nInput - minusOneQ + nLookaround) + 2);
+ tableid_t nApply = read_16u(data + offset + 6 + 2 * (nBack + nInput - minusOneQ + nLookaround));
+ checkLength(offset + 8 + 2 * (nBack + nInput - minusOneQ + nLookaround) + nApply * 4);
+
+ rule->matchCount = nBack + nInput + nLookaround;
+ rule->inputBegins = nBack;
+ rule->inputEnds = nBack + nInput;
+
+ NEW(rule->match, rule->matchCount);
+ tableid_t jj = 0;
+ for (tableid_t j = 0; j < nBack; j++) {
+ uint32_t gid = read_16u(data + offset + 2 + j * 2);
+ rule->match[jj++] = fn(data, tableLength, gid, offset, 1, maxGlyphs, userdata);
+ }
+ if (minusOne) {
+ rule->match[jj++] = fn(data, tableLength, startGID, offset, 2, maxGlyphs, userdata);
+ }
+ for (tableid_t j = 0; j < nInput - minusOneQ; j++) {
+ uint32_t gid = read_16u(data + offset + 4 + 2 * rule->inputBegins + j * 2);
+ rule->match[jj++] = fn(data, tableLength, gid, offset, 2, maxGlyphs, userdata);
+ }
+ for (tableid_t j = 0; j < nLookaround; j++) {
+ uint32_t gid = read_16u(data + offset + 6 + 2 * (rule->inputEnds - minusOneQ) + j * 2);
+ rule->match[jj++] = fn(data, tableLength, gid, offset, 3, maxGlyphs, userdata);
+ }
+ rule->applyCount = nApply;
+ NEW(rule->apply, rule->applyCount);
+ for (tableid_t j = 0; j < nApply; j++) {
+ rule->apply[j].index =
+ rule->inputBegins +
+ read_16u(data + offset + 8 + 2 * (rule->matchCount - minusOneQ) + j * 4);
+ rule->apply[j].lookup = Handle.fromIndex(
+ read_16u(data + offset + 8 + 2 * (rule->matchCount - minusOneQ) + j * 4 + 2));
+ }
+ reverseBacktracks(rule);
+ return rule;
+
+FAIL:
+ DELETE(deleteRule, rule);
+ return NULL;
+}
+static subtable_chaining *readChainingFormat1(subtable_chaining *subtable,
+ const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs) {
+ // Contextual Substitution Subtable, Simple.
+ checkLength(offset + 6);
+
+ uint16_t covOffset = offset + read_16u(data + offset + 2);
+ otl_Coverage *firstCoverage = Coverage.read(data, tableLength, covOffset);
+
+ tableid_t chainSubRuleSetCount = read_16u(data + offset + 4);
+ if (chainSubRuleSetCount != firstCoverage->numGlyphs) goto FAIL;
+ checkLength(offset + 6 + 2 * chainSubRuleSetCount);
+
+ tableid_t totalRules = 0;
+ for (tableid_t j = 0; j < chainSubRuleSetCount; j++) {
+ uint32_t srsOffset = offset + read_16u(data + offset + 6 + j * 2);
+ checkLength(srsOffset + 2);
+ totalRules += read_16u(data + srsOffset);
+ checkLength(srsOffset + 2 + 2 * read_16u(data + srsOffset));
+ }
+ subtable->rulesCount = totalRules;
+ NEW(subtable->rules, totalRules);
+
+ tableid_t jj = 0;
+ for (tableid_t j = 0; j < chainSubRuleSetCount; j++) {
+ uint32_t srsOffset = offset + read_16u(data + offset + 6 + j * 2);
+ tableid_t srsCount = read_16u(data + srsOffset);
+ for (tableid_t k = 0; k < srsCount; k++) {
+ uint32_t srOffset = srsOffset + read_16u(data + srsOffset + 2 + k * 2);
+ subtable->rules[jj] =
+ GeneralReadChainingRule(data, tableLength, srOffset, firstCoverage->glyphs[j].index,
+ true, singleCoverage, maxGlyphs, NULL);
+ jj += 1;
+ }
+ }
+
+ Coverage.free(firstCoverage);
+ return subtable;
+FAIL:
+ iSubtable_chaining.free(subtable);
+ return NULL;
+}
+static subtable_chaining *readChainingFormat2(subtable_chaining *subtable,
+ const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs) {
+ // Chaining Contextual Substitution Subtable, Class based.
+ checkLength(offset + 12);
+
+ classdefs *cds;
+ NEW(cds);
+ cds->bc = ClassDef.read(data, tableLength, offset + read_16u(data + offset + 4));
+ cds->ic = ClassDef.read(data, tableLength, offset + read_16u(data + offset + 6));
+ cds->fc = ClassDef.read(data, tableLength, offset + read_16u(data + offset + 8));
+
+ tableid_t chainSubClassSetCnt = read_16u(data + offset + 10);
+ checkLength(offset + 12 + 2 * chainSubClassSetCnt);
+
+ tableid_t totalRules = 0;
+ for (tableid_t j = 0; j < chainSubClassSetCnt; j++) {
+ uint32_t srcOffset = read_16u(data + offset + 12 + j * 2);
+ if (srcOffset) { totalRules += read_16u(data + offset + srcOffset); }
+ }
+ subtable->rulesCount = totalRules;
+ NEW(subtable->rules, totalRules);
+
+ tableid_t jj = 0;
+ for (tableid_t j = 0; j < chainSubClassSetCnt; j++) {
+ uint32_t srcOffset = read_16u(data + offset + 12 + j * 2);
+ if (srcOffset) {
+ tableid_t srsCount = read_16u(data + offset + srcOffset);
+ for (tableid_t k = 0; k < srsCount; k++) {
+ uint32_t dsrOffset = read_16u(data + offset + srcOffset + 2 + k * 2);
+ uint32_t srOffset = offset + srcOffset + dsrOffset;
+ subtable->rules[jj] = GeneralReadChainingRule(data, tableLength, srOffset, j, true,
+ classCoverage, maxGlyphs, cds);
+ jj += 1;
+ }
+ }
+ }
+
+ if (cds) {
+ if (cds->bc) ClassDef.free(cds->bc);
+ if (cds->ic) ClassDef.free(cds->ic);
+ if (cds->fc) ClassDef.free(cds->fc);
+ FREE(cds);
+ }
+ return subtable;
+FAIL:
+ iSubtable_chaining.free(subtable);
+ return NULL;
+}
+otl_Subtable *otl_read_chaining(const font_file_pointer data, uint32_t tableLength, uint32_t offset,
+ const glyphid_t maxGlyphs, const otfcc_Options *options) {
+ uint16_t format = 0;
+ subtable_chaining *subtable = iSubtable_chaining.create();
+ subtable->type = otl_chaining_poly;
+
+ checkLength(offset + 2);
+ format = read_16u(data + offset);
+ if (format == 1) {
+ return (otl_Subtable *)readChainingFormat1(subtable, data, tableLength, offset, maxGlyphs);
+ } else if (format == 2) {
+ return (otl_Subtable *)readChainingFormat2(subtable, data, tableLength, offset, maxGlyphs);
+ } else if (format == 3) {
+ // Chaining Contextual Substitution Subtable, Coverage based.
+ // This table has exactly one rule within it, and i love it.
+ subtable->rulesCount = 1;
+ NEW(subtable->rules, 1);
+ subtable->rules[0] = GeneralReadChainingRule(data, tableLength, offset + 2, 0, false,
+ format3Coverage, maxGlyphs, NULL);
+ return (otl_Subtable *)subtable;
+ }
+FAIL:
+ logWarning("Unsupported format %d.\n", format);
+ iSubtable_chaining.free(subtable);
+ return NULL;
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/common.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/common.h
new file mode 100644
index 00000000000..3fa1736e759
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/common.h
@@ -0,0 +1,15 @@
+#ifndef CARYLL_TABLE_OTL_SUBTABLES_COMMON_H
+#define CARYLL_TABLE_OTL_SUBTABLES_COMMON_H
+
+#include "support/util.h"
+#include "bk/bkgraph.h"
+#include "../../otl.h"
+
+typedef enum {
+ OTL_BH_NORMAL = 0,
+ OTL_BH_GSUB_VERT = 1
+} otl_BuildHeuristics;
+
+#define checkLength(offset) if (tableLength < offset) { goto FAIL; }
+
+#endif
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/extend.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/extend.c
new file mode 100644
index 00000000000..4b64449d0b4
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/extend.c
@@ -0,0 +1,35 @@
+#include "extend.h"
+
+// Extended tables are special
+// We will only deal with reading, and they will be flatten.
+static otl_Subtable *_caryll_read_otl_extend(font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, otl_LookupType BASIS,
+ const glyphid_t maxGlyphs,
+ const otfcc_Options *options) {
+ otl_Subtable *_subtable;
+ NEW(_subtable);
+ checkLength(subtableOffset + 8);
+ subtable_extend *subtable = &(_subtable->extend);
+ subtable->type = read_16u(data + subtableOffset + 2) + BASIS;
+ subtable->subtable = otfcc_readOtl_subtable(
+ data, tableLength, subtableOffset + read_32u(data + subtableOffset + 4), subtable->type,
+ maxGlyphs, options);
+ goto OK;
+FAIL:
+ FREE(_subtable);
+OK:
+ return _subtable;
+}
+
+otl_Subtable *otfcc_readOtl_gsub_extend(font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options) {
+ return _caryll_read_otl_extend(data, tableLength, subtableOffset, otl_type_gsub_unknown,
+ maxGlyphs, options);
+}
+otl_Subtable *otfcc_readOtl_gpos_extend(font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options) {
+ return _caryll_read_otl_extend(data, tableLength, subtableOffset, otl_type_gpos_unknown,
+ maxGlyphs, options);
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/extend.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/extend.h
new file mode 100644
index 00000000000..8ac3719a60c
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/extend.h
@@ -0,0 +1,13 @@
+#ifndef CARYLL_TABLE_OTL_EXTEND_H
+#define CARYLL_TABLE_OTL_EXTEND_H
+
+#include "common.h"
+
+otl_Subtable *otfcc_readOtl_gsub_extend(font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options);
+otl_Subtable *otfcc_readOtl_gpos_extend(font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options);
+
+#endif
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-common.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-common.c
new file mode 100644
index 00000000000..3c99772ee32
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-common.c
@@ -0,0 +1,214 @@
+#include "gpos-common.h"
+
+static void deleteMarkArrayItem(otl_MarkRecord *entry) {
+ Handle.dispose(&entry->glyph);
+}
+static caryll_ElementInterface(otl_MarkRecord) gss_typeinfo = {
+ .init = NULL, .copy = NULL, .dispose = deleteMarkArrayItem};
+
+caryll_standardVectorImpl(otl_MarkArray, otl_MarkRecord, gss_typeinfo, otl_iMarkArray);
+
+void otl_readMarkArray(otl_MarkArray *array, otl_Coverage *cov, font_file_pointer data,
+ uint32_t tableLength, uint32_t offset) {
+ checkLength(offset + 2);
+ glyphid_t markCount = read_16u(data + offset);
+ for (glyphid_t j = 0; j < markCount; j++) {
+ glyphclass_t markClass = read_16u(data + offset + 2 + j * 4);
+ uint16_t delta = read_16u(data + offset + 2 + j * 4 + 2);
+ if (delta) {
+ otl_iMarkArray.push(
+ array,
+ ((otl_MarkRecord){.glyph = Handle.dup(cov->glyphs[j]),
+ .markClass = markClass,
+ .anchor = otl_read_anchor(data, tableLength, offset + delta)}));
+ } else {
+ otl_iMarkArray.push(array, ((otl_MarkRecord){.glyph = Handle.dup(cov->glyphs[j]),
+ .markClass = markClass,
+ .anchor = otl_anchor_absent()}));
+ }
+ }
+FAIL:
+ return;
+}
+
+static int compare_classHash(otl_ClassnameHash *a, otl_ClassnameHash *b) {
+ return strcmp(a->className, b->className);
+}
+void otl_parseMarkArray(json_value *_marks, otl_MarkArray *array, otl_ClassnameHash **h,
+ const otfcc_Options *options) {
+ for (glyphid_t j = 0; j < _marks->u.object.length; j++) {
+ otl_MarkRecord mark;
+ char *gname = _marks->u.object.values[j].name;
+ json_value *anchorRecord = _marks->u.object.values[j].value;
+ mark.glyph = Handle.fromName(sdsnewlen(gname, _marks->u.object.values[j].name_length));
+ mark.markClass = 0;
+ mark.anchor = otl_anchor_absent();
+
+ if (!anchorRecord || anchorRecord->type != json_object) {
+ otl_iMarkArray.push(array, mark);
+ continue;
+ }
+ json_value *_className = json_obj_get_type(anchorRecord, "class", json_string);
+ if (!_className) {
+ otl_iMarkArray.push(array, mark);
+ continue;
+ }
+
+ sds className = sdsnewlen(_className->u.string.ptr, _className->u.string.length);
+ otl_ClassnameHash *s;
+ HASH_FIND_STR(*h, className, s);
+ if (!s) {
+ NEW(s);
+ s->className = className;
+ s->classID = HASH_COUNT(*h);
+ HASH_ADD_STR(*h, className, s);
+ } else {
+ sdsfree(className);
+ }
+ mark.markClass = s->classID;
+ mark.anchor.present = true;
+ mark.anchor.x = json_obj_getnum(anchorRecord, "x");
+ mark.anchor.y = json_obj_getnum(anchorRecord, "y");
+ otl_iMarkArray.push(array, mark);
+ }
+
+ HASH_SORT(*h, compare_classHash);
+ glyphid_t jAnchorIndex = 0;
+ otl_ClassnameHash *s;
+ foreach_hash(s, *h) {
+ s->classID = jAnchorIndex;
+ jAnchorIndex++;
+ }
+ for (glyphid_t j = 0; j < array->length; j++) {
+ if (!array->items[j].anchor.present) continue;
+ json_value *anchorRecord = _marks->u.object.values[j].value;
+ json_value *_className = json_obj_get_type(anchorRecord, "class", json_string);
+ sds className = sdsnewlen(_className->u.string.ptr, _className->u.string.length);
+ otl_ClassnameHash *s;
+ HASH_FIND_STR(*h, className, s);
+ if (s) {
+ array->items[j].markClass = s->classID;
+ } else {
+ array->items[j].markClass = 0;
+ }
+ sdsfree(className);
+ }
+}
+
+otl_Anchor otl_anchor_absent() {
+ otl_Anchor anchor = {.present = false, .x = 0, .y = 0};
+ return anchor;
+}
+otl_Anchor otl_read_anchor(font_file_pointer data, uint32_t tableLength, uint32_t offset) {
+ otl_Anchor anchor = {.present = false, .x = 0, .y = 0};
+ checkLength(offset + 6);
+ anchor.present = true;
+ anchor.x = read_16s(data + offset + 2);
+ anchor.y = read_16s(data + offset + 4);
+ return anchor;
+FAIL:
+ anchor.present = false;
+ anchor.x = 0;
+ anchor.y = 0;
+ return anchor;
+}
+json_value *otl_dump_anchor(otl_Anchor a) {
+ if (a.present) {
+ json_value *v = json_object_new(2);
+ json_object_push(v, "x", json_new_position(a.x));
+ json_object_push(v, "y", json_new_position(a.y));
+ return v;
+ } else {
+ return json_null_new();
+ }
+}
+otl_Anchor otl_parse_anchor(json_value *v) {
+ otl_Anchor anchor = {.present = false, .x = 0, .y = 0};
+ if (!v || v->type != json_object) return anchor;
+ anchor.present = true;
+ anchor.x = json_obj_getnum_fallback(v, "x", 0);
+ anchor.y = json_obj_getnum_fallback(v, "y", 0);
+ return anchor;
+}
+
+bk_Block *bkFromAnchor(otl_Anchor a) {
+ if (!a.present) return NULL;
+ return bk_new_Block(b16, 1, // format
+ b16, (int16_t)a.x, // x
+ b16, (int16_t)a.y, // y
+ bkover);
+}
+
+// GPOS position value constants
+const uint8_t FORMAT_DX = 1;
+const uint8_t FORMAT_DY = 2;
+const uint8_t FORMAT_DWIDTH = 4;
+const uint8_t FORMAT_DHEIGHT = 8;
+
+#define a(n) n + 0, n + 1, n + 1, n + 2 // 2 bits
+#define b(n) a(n + 0), a(n + 1), a(n + 1), a(n + 2) // 4 bits
+#define c(n) b(n + 0), b(n + 1), b(n + 1), b(n + 2) // 6 bits
+#define d(n) c(n + 0), c(n + 1), c(n + 1), c(n + 2) // 8 bits
+const uint8_t bits_in[0x100] = {d(0)};
+#undef d
+#undef c
+#undef b
+#undef a
+
+// Length of a position value in bytes
+uint8_t position_format_length(uint16_t format) {
+ return bits_in[format & 0xFF] << 1;
+}
+otl_PositionValue position_zero() {
+ otl_PositionValue v = {0.0, 0.0, 0.0, 0.0};
+ return v;
+}
+// Read a position value from SFNT
+otl_PositionValue read_gpos_value(font_file_pointer data, uint32_t tableLength, uint32_t offset,
+ uint16_t format) {
+ otl_PositionValue v = {0.0, 0.0, 0.0, 0.0};
+ if (tableLength < offset + position_format_length(format)) return v;
+ if (format & FORMAT_DX) { v.dx = read_16s(data + offset), offset += 2; };
+ if (format & FORMAT_DY) { v.dy = read_16s(data + offset), offset += 2; };
+ if (format & FORMAT_DWIDTH) { v.dWidth = read_16s(data + offset), offset += 2; };
+ if (format & FORMAT_DHEIGHT) { v.dHeight = read_16s(data + offset), offset += 2; };
+ return v;
+}
+json_value *gpos_dump_value(otl_PositionValue value) {
+ json_value *v = json_object_new(4);
+ if (value.dx) json_object_push(v, "dx", json_new_position(value.dx));
+ if (value.dy) json_object_push(v, "dy", json_new_position(value.dy));
+ if (value.dWidth) json_object_push(v, "dWidth", json_new_position(value.dWidth));
+ if (value.dHeight) json_object_push(v, "dHeight", json_new_position(value.dHeight));
+ return preserialize(v);
+}
+otl_PositionValue gpos_parse_value(json_value *pos) {
+ otl_PositionValue v = {0.0, 0.0, 0.0, 0.0};
+ if (!pos || pos->type != json_object) return v;
+ v.dx = json_obj_getnum(pos, "dx");
+ v.dy = json_obj_getnum(pos, "dy");
+ v.dWidth = json_obj_getnum(pos, "dWidth");
+ v.dHeight = json_obj_getnum(pos, "dHeight");
+ return v;
+}
+// The required format of a position value
+uint8_t required_position_format(otl_PositionValue v) {
+ return (v.dx ? FORMAT_DX : 0) | (v.dy ? FORMAT_DY : 0) | (v.dWidth ? FORMAT_DWIDTH : 0) |
+ (v.dHeight ? FORMAT_DHEIGHT : 0);
+}
+// Write gpos position value
+void write_gpos_value(caryll_Buffer *buf, otl_PositionValue v, uint16_t format) {
+ if (format & FORMAT_DX) bufwrite16b(buf, (int16_t)v.dx);
+ if (format & FORMAT_DY) bufwrite16b(buf, (int16_t)v.dy);
+ if (format & FORMAT_DWIDTH) bufwrite16b(buf, (int16_t)v.dWidth);
+ if (format & FORMAT_DHEIGHT) bufwrite16b(buf, (int16_t)v.dHeight);
+}
+
+bk_Block *bk_gpos_value(otl_PositionValue v, uint16_t format) {
+ bk_Block *b = bk_new_Block(bkover);
+ if (format & FORMAT_DX) bk_push(b, b16, (int16_t)v.dx, bkover);
+ if (format & FORMAT_DY) bk_push(b, b16, (int16_t)v.dy, bkover);
+ if (format & FORMAT_DWIDTH) bk_push(b, b16, (int16_t)v.dWidth, bkover);
+ if (format & FORMAT_DHEIGHT) bk_push(b, b16, (int16_t)v.dHeight, bkover);
+ return b;
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-common.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-common.h
new file mode 100644
index 00000000000..e232bbb6ecd
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-common.h
@@ -0,0 +1,43 @@
+#ifndef CARYLL_TABLE_OTL_GPOS_COMMON_H
+#define CARYLL_TABLE_OTL_GPOS_COMMON_H
+
+#include "common.h"
+
+// anchors
+otl_Anchor otl_anchor_absent(void);
+otl_Anchor otl_read_anchor(font_file_pointer data, uint32_t tableLength, uint32_t offset);
+json_value *otl_dump_anchor(otl_Anchor a);
+otl_Anchor otl_parse_anchor(json_value *v);
+bk_Block *bkFromAnchor(otl_Anchor a);
+
+// mark arrays
+typedef struct {
+ sds className;
+ glyphclass_t classID;
+ UT_hash_handle hh;
+} otl_ClassnameHash;
+
+void otl_readMarkArray(otl_MarkArray *array, otl_Coverage *cov, font_file_pointer data,
+ uint32_t tableLength, uint32_t offset);
+void otl_parseMarkArray(json_value *_marks, otl_MarkArray *array, otl_ClassnameHash **h,
+ const otfcc_Options *options);
+
+// position values
+extern const uint8_t FORMAT_DX;
+extern const uint8_t FORMAT_DY;
+extern const uint8_t FORMAT_DWIDTH;
+extern const uint8_t FORMAT_DHEIGHT;
+extern const uint8_t bits_in[0x100];
+
+uint8_t position_format_length(uint16_t format);
+otl_PositionValue position_zero(void);
+otl_PositionValue read_gpos_value(font_file_pointer data, uint32_t tableLength, uint32_t offset,
+ uint16_t format);
+uint8_t required_position_format(otl_PositionValue v);
+void write_gpos_value(caryll_Buffer *buf, otl_PositionValue v, uint16_t format);
+bk_Block *bk_gpos_value(otl_PositionValue v, uint16_t format);
+
+json_value *gpos_dump_value(otl_PositionValue value);
+otl_PositionValue gpos_parse_value(json_value *pos);
+
+#endif
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-cursive.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-cursive.c
new file mode 100644
index 00000000000..f75fc487a20
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-cursive.c
@@ -0,0 +1,101 @@
+#include "gpos-cursive.h"
+#include "gpos-common.h"
+
+static void deleteGposCursiveEntry(otl_GposCursiveEntry *entry) {
+ Handle.dispose(&entry->target);
+}
+
+static caryll_ElementInterface(otl_GposCursiveEntry) gss_typeinfo = {
+ .init = NULL, .copy = NULL, .dispose = deleteGposCursiveEntry};
+
+caryll_standardVectorImpl(subtable_gpos_cursive, otl_GposCursiveEntry, gss_typeinfo,
+ iSubtable_gpos_cursive);
+
+otl_Subtable *otl_read_gpos_cursive(const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options) {
+ subtable_gpos_cursive *subtable = iSubtable_gpos_cursive.create();
+ otl_Coverage *targets = NULL;
+
+ checkLength(offset + 6);
+
+ targets = Coverage.read(data, tableLength, offset + read_16u(data + offset + 2));
+ if (!targets || targets->numGlyphs == 0) goto FAIL;
+
+ glyphid_t valueCount = read_16u(data + offset + 4);
+ checkLength(offset + 6 + 4 * valueCount);
+ if (valueCount != targets->numGlyphs) goto FAIL;
+
+ for (glyphid_t j = 0; j < valueCount; j++) {
+ uint16_t enterOffset = read_16u(data + offset + 6 + 4 * j);
+ uint16_t exitOffset = read_16u(data + offset + 6 + 4 * j + 2);
+ otl_Anchor enter = otl_anchor_absent();
+ otl_Anchor exit = otl_anchor_absent();
+ if (enterOffset) { enter = otl_read_anchor(data, tableLength, offset + enterOffset); }
+ if (exitOffset) { exit = otl_read_anchor(data, tableLength, offset + exitOffset); }
+ iSubtable_gpos_cursive.push(
+ subtable, ((otl_GposCursiveEntry){
+ .target = Handle.dup(targets->glyphs[j]), .enter = enter, .exit = exit}));
+ }
+ if (targets) Coverage.free(targets);
+ return (otl_Subtable *)subtable;
+FAIL:
+ if (targets) Coverage.free(targets);
+ iSubtable_gpos_cursive.free(subtable);
+ return NULL;
+}
+
+json_value *otl_gpos_dump_cursive(const otl_Subtable *_subtable) {
+ const subtable_gpos_cursive *subtable = &(_subtable->gpos_cursive);
+ json_value *st = json_object_new(subtable->length);
+ for (glyphid_t j = 0; j < subtable->length; j++) {
+ json_value *rec = json_object_new(2);
+ json_object_push(rec, "enter", otl_dump_anchor(subtable->items[j].enter));
+ json_object_push(rec, "exit", otl_dump_anchor(subtable->items[j].exit));
+ json_object_push(st, subtable->items[j].target.name, preserialize(rec));
+ }
+ return st;
+}
+
+otl_Subtable *otl_gpos_parse_cursive(const json_value *_subtable, const otfcc_Options *options) {
+ subtable_gpos_cursive *subtable = iSubtable_gpos_cursive.create();
+ for (glyphid_t j = 0; j < _subtable->u.object.length; j++) {
+ if (_subtable->u.object.values[j].value &&
+ _subtable->u.object.values[j].value->type == json_object) {
+ sds gname = sdsnewlen(_subtable->u.object.values[j].name,
+ _subtable->u.object.values[j].name_length);
+ iSubtable_gpos_cursive.push(
+ subtable, ((otl_GposCursiveEntry){
+ .target = Handle.fromName(gname),
+ .enter = otl_parse_anchor(
+ json_obj_get(_subtable->u.object.values[j].value, "enter")),
+ .exit = otl_parse_anchor(
+ json_obj_get(_subtable->u.object.values[j].value, "exit")),
+ }));
+ }
+ }
+ return (otl_Subtable *)subtable;
+}
+
+caryll_Buffer *otfcc_build_gpos_cursive(const otl_Subtable *_subtable,
+ otl_BuildHeuristics heuristics) {
+ const subtable_gpos_cursive *subtable = &(_subtable->gpos_cursive);
+ otl_Coverage *cov = Coverage.create();
+ for (glyphid_t j = 0; j < subtable->length; j++) {
+ Coverage.push(cov, Handle.dup(subtable->items[j].target));
+ }
+
+ bk_Block *root = bk_new_Block(b16, 1, // format
+ p16, bk_newBlockFromBuffer(Coverage.build(cov)), // Coverage
+ b16, subtable->length, // EntryExitCount
+ bkover);
+ for (glyphid_t j = 0; j < subtable->length; j++) {
+ bk_push(root, // EntryExitRecord[.]
+ p16, bkFromAnchor(subtable->items[j].enter), // enter
+ p16, bkFromAnchor(subtable->items[j].exit), // exit
+ bkover);
+ }
+ Coverage.free(cov);
+
+ return bk_build_Block(root);
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-cursive.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-cursive.h
new file mode 100644
index 00000000000..e0736de4c46
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-cursive.h
@@ -0,0 +1,13 @@
+#ifndef CARYLL_TABLE_OTL_GPOS_CURSIVE_H
+#define CARYLL_TABLE_OTL_GPOS_CURSIVE_H
+
+#include "common.h"
+
+otl_Subtable *otl_read_gpos_cursive(const font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options);
+json_value *otl_gpos_dump_cursive(const otl_Subtable *_subtable);
+otl_Subtable *otl_gpos_parse_cursive(const json_value *_subtable, const otfcc_Options *options);
+caryll_Buffer *otfcc_build_gpos_cursive(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics);
+
+#endif
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-ligature.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-ligature.c
new file mode 100644
index 00000000000..45bf16b80fa
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-ligature.c
@@ -0,0 +1,241 @@
+#include "gpos-mark-to-ligature.h"
+#include "gpos-common.h"
+
+static void deleteLigArrayItem(otl_LigatureBaseRecord *entry) {
+ Handle.dispose(&entry->glyph);
+ if (entry->anchors) {
+ for (glyphid_t k = 0; k < entry->componentCount; k++)
+ FREE(entry->anchors[k]);
+ FREE(entry->anchors);
+ }
+}
+static caryll_ElementInterface(otl_LigatureBaseRecord) la_typeinfo = {
+ .init = NULL, .copy = NULL, .dispose = deleteLigArrayItem};
+
+caryll_standardVectorImpl(otl_LigatureArray, otl_LigatureBaseRecord, la_typeinfo,
+ otl_iLigatureArray);
+
+static INLINE void initMarkToLigature(subtable_gpos_markToLigature *subtable) {
+ otl_iMarkArray.init(&subtable->markArray);
+ otl_iLigatureArray.init(&subtable->ligArray);
+}
+static INLINE void disposeMarkToLigature(subtable_gpos_markToLigature *subtable) {
+ otl_iMarkArray.dispose(&subtable->markArray);
+ otl_iLigatureArray.dispose(&subtable->ligArray);
+}
+
+caryll_standardRefType(subtable_gpos_markToLigature, iSubtable_gpos_markToLigature,
+ initMarkToLigature, disposeMarkToLigature);
+
+otl_Subtable *otl_read_gpos_markToLigature(const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options) {
+ subtable_gpos_markToLigature *subtable = iSubtable_gpos_markToLigature.create();
+ otl_Coverage *marks = NULL;
+ otl_Coverage *bases = NULL;
+ if (tableLength < offset + 12) goto FAIL;
+
+ marks = Coverage.read(data, tableLength, offset + read_16u(data + offset + 2));
+ bases = Coverage.read(data, tableLength, offset + read_16u(data + offset + 4));
+ if (!marks || marks->numGlyphs == 0 || !bases || bases->numGlyphs == 0) goto FAIL;
+ subtable->classCount = read_16u(data + offset + 6);
+
+ uint32_t markArrayOffset = offset + read_16u(data + offset + 8);
+ otl_readMarkArray(&subtable->markArray, marks, data, tableLength, markArrayOffset);
+
+ uint32_t ligArrayOffset = offset + read_16u(data + offset + 10);
+ checkLength(ligArrayOffset + 2 + 2 * bases->numGlyphs);
+ if (read_16u(data + ligArrayOffset) != bases->numGlyphs) goto FAIL;
+
+ for (glyphid_t j = 0; j < bases->numGlyphs; j++) {
+ otl_LigatureBaseRecord lig;
+ lig.glyph = Handle.dup(bases->glyphs[j]);
+ uint32_t ligAttachOffset = ligArrayOffset + read_16u(data + ligArrayOffset + 2 + j * 2);
+ checkLength(ligAttachOffset + 2);
+ lig.componentCount = read_16u(data + ligAttachOffset);
+
+ checkLength(ligAttachOffset + 2 + 2 * lig.componentCount * subtable->classCount);
+ NEW(lig.anchors, lig.componentCount);
+
+ uint32_t _offset = ligAttachOffset + 2;
+ for (glyphid_t k = 0; k < lig.componentCount; k++) {
+ NEW(lig.anchors[k], subtable->classCount);
+ for (glyphclass_t m = 0; m < subtable->classCount; m++) {
+ uint32_t anchorOffset = read_16u(data + _offset);
+ if (anchorOffset) {
+ lig.anchors[k][m] =
+ otl_read_anchor(data, tableLength, ligAttachOffset + anchorOffset);
+ } else {
+ lig.anchors[k][m] = otl_anchor_absent();
+ }
+ _offset += 2;
+ }
+ }
+ otl_iLigatureArray.push(&subtable->ligArray, lig);
+ }
+ if (marks) Coverage.free(marks);
+ if (bases) Coverage.free(bases);
+ return (otl_Subtable *)subtable;
+FAIL:
+ if (marks) Coverage.free(marks);
+ if (bases) Coverage.free(bases);
+ iSubtable_gpos_markToLigature.free(subtable);
+ return NULL;
+}
+
+json_value *otl_gpos_dump_markToLigature(const otl_Subtable *st) {
+ const subtable_gpos_markToLigature *subtable = &(st->gpos_markToLigature);
+ json_value *_subtable = json_object_new(3);
+ json_value *_marks = json_object_new(subtable->markArray.length);
+ json_value *_bases = json_object_new(subtable->ligArray.length);
+ for (glyphid_t j = 0; j < subtable->markArray.length; j++) {
+ json_value *_mark = json_object_new(3);
+ sds markClassName = sdscatfmt(sdsempty(), "ac_%i", subtable->markArray.items[j].markClass);
+ json_object_push(_mark, "class",
+ json_string_new_length((uint32_t)sdslen(markClassName), markClassName));
+ sdsfree(markClassName);
+ json_object_push(_mark, "x", json_integer_new(subtable->markArray.items[j].anchor.x));
+ json_object_push(_mark, "y", json_integer_new(subtable->markArray.items[j].anchor.y));
+ json_object_push(_marks, subtable->markArray.items[j].glyph.name, preserialize(_mark));
+ }
+ for (glyphid_t j = 0; j < subtable->ligArray.length; j++) {
+ otl_LigatureBaseRecord *base = &subtable->ligArray.items[j];
+ json_value *_base = json_array_new(base->componentCount);
+ for (glyphid_t k = 0; k < base->componentCount; k++) {
+ json_value *_bk = json_object_new(subtable->classCount);
+ for (glyphclass_t m = 0; m < subtable->classCount; m++) {
+ if (base->anchors[k][m].present) {
+ json_value *_anchor = json_object_new(2);
+ json_object_push(_anchor, "x", json_integer_new(base->anchors[k][m].x));
+ json_object_push(_anchor, "y", json_integer_new(base->anchors[k][m].y));
+ sds markClassName = sdscatfmt(sdsempty(), "ac_%i", m);
+ json_object_push_length(_bk, (uint32_t)sdslen(markClassName), markClassName,
+ _anchor);
+ sdsfree(markClassName);
+ }
+ }
+ json_array_push(_base, _bk);
+ }
+ json_object_push(_bases, base->glyph.name, preserialize(_base));
+ }
+ json_object_push(_subtable, "classCount", json_integer_new(subtable->classCount));
+ json_object_push(_subtable, "marks", _marks);
+ json_object_push(_subtable, "bases", _bases);
+ return _subtable;
+}
+
+static void parseBases(json_value *_bases, subtable_gpos_markToLigature *subtable,
+ otl_ClassnameHash **h, const otfcc_Options *options) {
+ glyphclass_t classCount = HASH_COUNT(*h);
+
+ for (glyphid_t j = 0; j < _bases->u.object.length; j++) {
+ char *gname = _bases->u.object.values[j].name;
+ otl_LigatureBaseRecord lig;
+ lig.componentCount = 0;
+ lig.anchors = NULL;
+ lig.glyph = Handle.fromName(
+ sdsnewlen(_bases->u.object.values[j].name, _bases->u.object.values[j].name_length));
+
+ json_value *baseRecord = _bases->u.object.values[j].value;
+ if (!baseRecord || baseRecord->type != json_array) {
+ otl_iLigatureArray.push(&subtable->ligArray, lig);
+ continue;
+ }
+ lig.componentCount = baseRecord->u.array.length;
+
+ NEW(lig.anchors, lig.componentCount);
+
+ for (glyphid_t k = 0; k < lig.componentCount; k++) {
+ json_value *_componentRecord = baseRecord->u.array.values[k];
+ NEW(lig.anchors[k], classCount);
+ for (glyphclass_t m = 0; m < classCount; m++) {
+ lig.anchors[k][m] = otl_anchor_absent();
+ }
+ if (!_componentRecord || _componentRecord->type != json_object) { continue; }
+ for (glyphclass_t m = 0; m < _componentRecord->u.object.length; m++) {
+ sds className = sdsnewlen(_componentRecord->u.object.values[m].name,
+ _componentRecord->u.object.values[m].name_length);
+ otl_ClassnameHash *s;
+ HASH_FIND_STR(*h, className, s);
+ if (!s) {
+ logWarning("[OTFCC-fea] Invalid anchor class name <%s> for /%s. This base "
+ "anchor is ignored.\n",
+ className, gname);
+ goto NEXT;
+ }
+ lig.anchors[k][s->classID] =
+ otl_parse_anchor(_componentRecord->u.object.values[m].value);
+
+ NEXT:
+ sdsfree(className);
+ }
+ }
+ otl_iLigatureArray.push(&subtable->ligArray, lig);
+ }
+}
+otl_Subtable *otl_gpos_parse_markToLigature(const json_value *_subtable,
+ const otfcc_Options *options) {
+ json_value *_marks = json_obj_get_type(_subtable, "marks", json_object);
+ json_value *_bases = json_obj_get_type(_subtable, "bases", json_object);
+ if (!_marks || !_bases) return NULL;
+ subtable_gpos_markToLigature *st = iSubtable_gpos_markToLigature.create();
+ otl_ClassnameHash *h = NULL;
+ otl_parseMarkArray(_marks, &st->markArray, &h, options);
+ st->classCount = HASH_COUNT(h);
+ parseBases(_bases, st, &h, options);
+
+ otl_ClassnameHash *s, *tmp;
+ HASH_ITER(hh, h, s, tmp) {
+ HASH_DEL(h, s);
+ sdsfree(s->className);
+ FREE(s);
+ }
+
+ return (otl_Subtable *)st;
+}
+
+caryll_Buffer *otfcc_build_gpos_markToLigature(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics) {
+ const subtable_gpos_markToLigature *subtable = &(_subtable->gpos_markToLigature);
+ otl_Coverage *marks = Coverage.create();
+ for (glyphid_t j = 0; j < subtable->markArray.length; j++) {
+ Coverage.push(marks, Handle.dup(subtable->markArray.items[j].glyph));
+ }
+ otl_Coverage *bases = Coverage.create();
+ for (glyphid_t j = 0; j < subtable->ligArray.length; j++) {
+ Coverage.push(bases, Handle.dup(subtable->ligArray.items[j].glyph));
+ }
+
+ bk_Block *root = bk_new_Block(b16, 1, // format
+ p16, bk_newBlockFromBuffer(Coverage.build(marks)), // markCoverage
+ p16, bk_newBlockFromBuffer(Coverage.build(bases)), // baseCoverage
+ b16, subtable->classCount, // classCont
+ bkover);
+
+ bk_Block *markArray = bk_new_Block(b16, subtable->markArray.length, // markCount
+ bkover);
+ for (glyphid_t j = 0; j < subtable->markArray.length; j++) {
+ bk_push(markArray, // markArray item
+ b16, subtable->markArray.items[j].markClass, // markClass
+ p16, bkFromAnchor(subtable->markArray.items[j].anchor), // Anchor
+ bkover);
+ }
+
+ bk_Block *ligatureArray = bk_new_Block(b16, subtable->ligArray.length, bkover);
+ for (glyphid_t j = 0; j < subtable->ligArray.length; j++) {
+ bk_Block *attach =
+ bk_new_Block(b16, subtable->ligArray.items[j].componentCount, // componentCount
+ bkover);
+ for (glyphid_t k = 0; k < subtable->ligArray.items[j].componentCount; k++) {
+ for (glyphclass_t m = 0; m < subtable->classCount; m++) {
+ bk_push(attach, p16, bkFromAnchor(subtable->ligArray.items[j].anchors[k][m]),
+ bkover);
+ }
+ }
+ bk_push(ligatureArray, p16, attach, bkover);
+ }
+
+ bk_push(root, p16, markArray, p16, ligatureArray, bkover);
+ Coverage.free(marks);
+ Coverage.free(bases);
+ return bk_build_Block(root);
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-ligature.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-ligature.h
new file mode 100644
index 00000000000..53de1acbd19
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-ligature.h
@@ -0,0 +1,14 @@
+#ifndef CARYLL_TABLE_OTL_GPOS_MARK_TO_LIGATURE_H
+#define CARYLL_TABLE_OTL_GPOS_MARK_TO_LIGATURE_H
+
+#include "common.h"
+
+otl_Subtable *otl_read_gpos_markToLigature(const font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options);
+json_value *otl_gpos_dump_markToLigature(const otl_Subtable *st);
+otl_Subtable *otl_gpos_parse_markToLigature(const json_value *_subtable,
+ const otfcc_Options *options);
+caryll_Buffer *otfcc_build_gpos_markToLigature(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics);
+
+#endif
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-single.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-single.c
new file mode 100644
index 00000000000..cf55cde82dc
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-single.c
@@ -0,0 +1,203 @@
+#include "gpos-mark-to-single.h"
+#include "gpos-common.h"
+
+static void deleteBaseArrayItem(otl_BaseRecord *entry) {
+ Handle.dispose(&entry->glyph);
+ FREE(entry->anchors);
+}
+static caryll_ElementInterface(otl_BaseRecord) ba_typeinfo = {
+ .init = NULL, .copy = NULL, .dispose = deleteBaseArrayItem};
+
+caryll_standardVectorImpl(otl_BaseArray, otl_BaseRecord, ba_typeinfo, otl_iBaseArray);
+
+static INLINE void initMarkToSingle(subtable_gpos_markToSingle *subtable) {
+ otl_iMarkArray.init(&subtable->markArray);
+ otl_iBaseArray.init(&subtable->baseArray);
+}
+static INLINE void disposeMarkToSingle(subtable_gpos_markToSingle *subtable) {
+ otl_iMarkArray.dispose(&subtable->markArray);
+ otl_iBaseArray.dispose(&subtable->baseArray);
+}
+
+caryll_standardRefType(subtable_gpos_markToSingle, iSubtable_gpos_markToSingle, initMarkToSingle,
+ disposeMarkToSingle);
+
+otl_Subtable *otl_read_gpos_markToSingle(const font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options) {
+
+ subtable_gpos_markToSingle *subtable = iSubtable_gpos_markToSingle.create();
+ otl_Coverage *marks = NULL;
+ otl_Coverage *bases = NULL;
+
+ if (tableLength < subtableOffset + 12) goto FAIL;
+ marks = Coverage.read(data, tableLength, subtableOffset + read_16u(data + subtableOffset + 2));
+ bases = Coverage.read(data, tableLength, subtableOffset + read_16u(data + subtableOffset + 4));
+ if (!marks || marks->numGlyphs == 0 || !bases || bases->numGlyphs == 0) goto FAIL;
+
+ subtable->classCount = read_16u(data + subtableOffset + 6);
+ uint32_t markArrayOffset = subtableOffset + read_16u(data + subtableOffset + 8);
+ otl_readMarkArray(&subtable->markArray, marks, data, tableLength, markArrayOffset);
+
+ uint32_t baseArrayOffset = subtableOffset + read_16u(data + subtableOffset + 10);
+ checkLength(baseArrayOffset + 2 + 2 * bases->numGlyphs * subtable->classCount);
+ if (read_16u(data + baseArrayOffset) != bases->numGlyphs) goto FAIL;
+
+ uint32_t _offset = baseArrayOffset + 2;
+ for (glyphid_t j = 0; j < bases->numGlyphs; j++) {
+ otl_Anchor *baseAnchors;
+ NEW(baseAnchors, subtable->classCount);
+ for (glyphclass_t k = 0; k < subtable->classCount; k++) {
+ if (read_16u(data + _offset)) {
+ baseAnchors[k] =
+ otl_read_anchor(data, tableLength, baseArrayOffset + read_16u(data + _offset));
+ } else {
+ baseAnchors[k] = otl_anchor_absent();
+ }
+ _offset += 2;
+ }
+ otl_iBaseArray.push(
+ &subtable->baseArray,
+ ((otl_BaseRecord){.glyph = Handle.dup(bases->glyphs[j]), .anchors = baseAnchors}));
+ }
+ if (marks) Coverage.free(marks);
+ if (bases) Coverage.free(bases);
+ return (otl_Subtable *)subtable;
+FAIL:
+ iSubtable_gpos_markToSingle.free(subtable);
+ return NULL;
+}
+
+json_value *otl_gpos_dump_markToSingle(const otl_Subtable *st) {
+ const subtable_gpos_markToSingle *subtable = &(st->gpos_markToSingle);
+ json_value *_subtable = json_object_new(3);
+ json_value *_marks = json_object_new(subtable->markArray.length);
+ json_value *_bases = json_object_new(subtable->baseArray.length);
+ for (glyphid_t j = 0; j < subtable->markArray.length; j++) {
+ json_value *_mark = json_object_new(3);
+ sds markClassName =
+ sdscatfmt(sdsempty(), "anchor%i", subtable->markArray.items[j].markClass);
+ json_object_push(_mark, "class",
+ json_string_new_length((uint32_t)sdslen(markClassName), markClassName));
+ sdsfree(markClassName);
+ json_object_push(_mark, "x", json_integer_new(subtable->markArray.items[j].anchor.x));
+ json_object_push(_mark, "y", json_integer_new(subtable->markArray.items[j].anchor.y));
+ json_object_push(_marks, subtable->markArray.items[j].glyph.name, preserialize(_mark));
+ }
+ for (glyphid_t j = 0; j < subtable->baseArray.length; j++) {
+ json_value *_base = json_object_new(subtable->classCount);
+ for (glyphclass_t k = 0; k < subtable->classCount; k++) {
+ if (subtable->baseArray.items[j].anchors[k].present) {
+ json_value *_anchor = json_object_new(2);
+ json_object_push(_anchor, "x",
+ json_integer_new(subtable->baseArray.items[j].anchors[k].x));
+ json_object_push(_anchor, "y",
+ json_integer_new(subtable->baseArray.items[j].anchors[k].y));
+ sds markClassName = sdscatfmt(sdsempty(), "anchor%i", k);
+ json_object_push_length(_base, (uint32_t)sdslen(markClassName), markClassName,
+ _anchor);
+ sdsfree(markClassName);
+ }
+ }
+ json_object_push(_bases, subtable->baseArray.items[j].glyph.name, preserialize(_base));
+ }
+ json_object_push(_subtable, "marks", _marks);
+ json_object_push(_subtable, "bases", _bases);
+ return _subtable;
+}
+
+static void parseBases(json_value *_bases, subtable_gpos_markToSingle *subtable,
+ otl_ClassnameHash **h, const otfcc_Options *options) {
+ glyphclass_t classCount = HASH_COUNT(*h);
+ for (glyphid_t j = 0; j < _bases->u.object.length; j++) {
+ char *gname = _bases->u.object.values[j].name;
+ otl_BaseRecord base;
+ base.glyph = Handle.fromName(sdsnewlen(gname, _bases->u.object.values[j].name_length));
+ NEW(base.anchors, classCount);
+ for (glyphclass_t k = 0; k < classCount; k++) {
+ base.anchors[k] = otl_anchor_absent();
+ }
+ json_value *baseRecord = _bases->u.object.values[j].value;
+ if (!baseRecord || baseRecord->type != json_object) {
+ otl_iBaseArray.push(&subtable->baseArray, base);
+ continue;
+ }
+
+ for (glyphclass_t k = 0; k < baseRecord->u.object.length; k++) {
+ sds className = sdsnewlen(baseRecord->u.object.values[k].name,
+ baseRecord->u.object.values[k].name_length);
+ otl_ClassnameHash *s;
+ HASH_FIND_STR(*h, className, s);
+ if (!s) {
+ logWarning("[OTFCC-fea] Invalid anchor class name <%s> for /%s. This base anchor "
+ "is ignored.\n",
+ className, gname);
+ goto NEXT;
+ }
+ base.anchors[s->classID] = otl_parse_anchor(baseRecord->u.object.values[k].value);
+ NEXT:
+ sdsfree(className);
+ }
+ otl_iBaseArray.push(&subtable->baseArray, base);
+ }
+}
+otl_Subtable *otl_gpos_parse_markToSingle(const json_value *_subtable,
+ const otfcc_Options *options) {
+ json_value *_marks = json_obj_get_type(_subtable, "marks", json_object);
+ json_value *_bases = json_obj_get_type(_subtable, "bases", json_object);
+ if (!_marks || !_bases) return NULL;
+ subtable_gpos_markToSingle *st = iSubtable_gpos_markToSingle.create();
+ otl_ClassnameHash *h = NULL;
+ otl_parseMarkArray(_marks, &st->markArray, &h, options);
+ st->classCount = HASH_COUNT(h);
+ parseBases(_bases, st, &h, options);
+
+ otl_ClassnameHash *s, *tmp;
+ HASH_ITER(hh, h, s, tmp) {
+ HASH_DEL(h, s);
+ sdsfree(s->className);
+ FREE(s);
+ }
+
+ return (otl_Subtable *)st;
+}
+
+caryll_Buffer *otfcc_build_gpos_markToSingle(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics) {
+ const subtable_gpos_markToSingle *subtable = &(_subtable->gpos_markToSingle);
+ otl_Coverage *marks = Coverage.create();
+ for (glyphid_t j = 0; j < subtable->markArray.length; j++) {
+ Coverage.push(marks, Handle.dup(subtable->markArray.items[j].glyph));
+ }
+ otl_Coverage *bases = Coverage.create();
+ for (glyphid_t j = 0; j < subtable->baseArray.length; j++) {
+ Coverage.push(bases, Handle.dup(subtable->baseArray.items[j].glyph));
+ }
+
+ bk_Block *root = bk_new_Block(b16, 1, // format
+ p16, bk_newBlockFromBuffer(Coverage.build(marks)), // markCoverage
+ p16, bk_newBlockFromBuffer(Coverage.build(bases)), // baseCoverage
+ b16, subtable->classCount, // classCont
+ bkover);
+
+ bk_Block *markArray = bk_new_Block(b16, subtable->markArray.length, // markCount
+ bkover);
+ for (glyphid_t j = 0; j < subtable->markArray.length; j++) {
+ bk_push(markArray, // markArray item
+ b16, subtable->markArray.items[j].markClass, // markClass
+ p16, bkFromAnchor(subtable->markArray.items[j].anchor), // Anchor
+ bkover);
+ }
+
+ bk_Block *baseArray = bk_new_Block(b16, subtable->baseArray.length, // baseCount
+ bkover);
+ for (glyphid_t j = 0; j < subtable->baseArray.length; j++) {
+ for (glyphclass_t k = 0; k < subtable->classCount; k++) {
+ bk_push(baseArray, p16, bkFromAnchor(subtable->baseArray.items[j].anchors[k]), bkover);
+ }
+ }
+
+ bk_push(root, p16, markArray, p16, baseArray, bkover);
+ Coverage.free(marks);
+ Coverage.free(bases);
+ return bk_build_Block(root);
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-single.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-single.h
new file mode 100644
index 00000000000..e5e7a8792ea
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-mark-to-single.h
@@ -0,0 +1,14 @@
+#ifndef CARYLL_TABLE_OTL_GPOS_MARK_TO_SINGLE_H
+#define CARYLL_TABLE_OTL_GPOS_MARK_TO_SINGLE_H
+
+#include "common.h"
+
+otl_Subtable *otl_read_gpos_markToSingle(const font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options);
+json_value *otl_gpos_dump_markToSingle(const otl_Subtable *st);
+otl_Subtable *otl_gpos_parse_markToSingle(const json_value *_subtable,
+ const otfcc_Options *options);
+caryll_Buffer *otfcc_build_gpos_markToSingle(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics);
+
+#endif
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-pair.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-pair.c
new file mode 100644
index 00000000000..87a113096d2
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-pair.c
@@ -0,0 +1,406 @@
+#include "gpos-pair.h"
+#include "gpos-common.h"
+
+static INLINE void initGposPair(subtable_gpos_pair *subtable) {
+ subtable->first = NULL;
+ subtable->second = NULL;
+ subtable->firstValues = NULL;
+ subtable->secondValues = NULL;
+}
+static INLINE void disposeGposPair(subtable_gpos_pair *subtable) {
+ if (subtable->firstValues) {
+ for (glyphclass_t j = 0; j <= subtable->first->maxclass; j++) {
+ FREE(subtable->firstValues[j]);
+ }
+ FREE(subtable->firstValues);
+ }
+ if (subtable->secondValues) {
+ for (glyphclass_t j = 0; j <= subtable->first->maxclass; j++) {
+ FREE(subtable->secondValues[j]);
+ }
+ FREE(subtable->secondValues);
+ }
+ DELETE(ClassDef.free, subtable->first);
+ DELETE(ClassDef.free, subtable->second);
+}
+
+caryll_standardRefType(subtable_gpos_pair, iSubtable_gpos_pair, initGposPair, disposeGposPair);
+
+typedef struct {
+ int gid;
+ int cid;
+ UT_hash_handle hh;
+} pair_classifier_hash;
+
+otl_Subtable *otl_read_gpos_pair(const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options) {
+ subtable_gpos_pair *subtable = iSubtable_gpos_pair.create();
+
+ checkLength(offset + 2);
+ uint16_t subtableFormat = read_16u(data + offset);
+ if (subtableFormat == 1) {
+ // pair adjustment by individuals
+ otl_Coverage *cov = Coverage.read(data, tableLength, offset + read_16u(data + offset + 2));
+ NEW(subtable->first);
+ subtable->first->numGlyphs = cov->numGlyphs;
+ subtable->first->maxclass = cov->numGlyphs - 1;
+ subtable->first->glyphs = cov->glyphs;
+ NEW(subtable->first->classes, cov->numGlyphs);
+ for (glyphid_t j = 0; j < cov->numGlyphs; j++) {
+ subtable->first->classes[j] = j;
+ }
+ FREE(cov);
+
+ uint16_t format1 = read_16u(data + offset + 4);
+ uint16_t format2 = read_16u(data + offset + 6);
+ uint8_t len1 = position_format_length(format1);
+ uint8_t len2 = position_format_length(format2);
+
+ glyphid_t pairSetCount = read_16u(data + offset + 8);
+ if (pairSetCount != subtable->first->numGlyphs) goto FAIL;
+ checkLength(offset + 10 + 2 * pairSetCount);
+
+ // pass 1: check length
+ for (glyphid_t j = 0; j < pairSetCount; j++) {
+ uint32_t pairSetOffset = offset + read_16u(data + offset + 10 + 2 * j);
+ checkLength(pairSetOffset + 2);
+ glyphid_t pairCount = read_16u(data + pairSetOffset);
+ checkLength(pairSetOffset + 2 + (2 + len1 + len2) * pairCount);
+ }
+ // pass 2: make hashtable about second glyphs to construct a classdec
+ pair_classifier_hash *h = NULL;
+ for (glyphid_t j = 0; j < pairSetCount; j++) {
+ uint32_t pairSetOffset = offset + read_16u(data + offset + 10 + 2 * j);
+ glyphid_t pairCount = read_16u(data + pairSetOffset);
+ for (glyphid_t k = 0; k < pairCount; k++) {
+ int second = read_16u(data + pairSetOffset + 2 + (2 + len1 + len2) * k);
+ pair_classifier_hash *s;
+ HASH_FIND_INT(h, &second, s);
+ if (!s) {
+ NEW(s);
+ s->gid = second;
+ s->cid = HASH_COUNT(h) + 1;
+ HASH_ADD_INT(h, gid, s);
+ }
+ }
+ }
+ // pass 3: make the matrix
+ NEW(subtable->second);
+ subtable->second->numGlyphs = HASH_COUNT(h);
+ subtable->second->maxclass = HASH_COUNT(h);
+ NEW(subtable->second->classes, subtable->second->numGlyphs);
+ NEW(subtable->second->glyphs, subtable->second->numGlyphs);
+ glyphclass_t class2Count = subtable->second->maxclass + 1;
+
+ NEW(subtable->firstValues, subtable->first->maxclass + 1);
+ NEW(subtable->secondValues, subtable->first->maxclass + 1);
+ for (glyphclass_t j = 0; j <= subtable->first->maxclass; j++) {
+ NEW(subtable->firstValues[j], class2Count);
+ NEW(subtable->secondValues[j], class2Count);
+ for (glyphclass_t k = 0; k < class2Count; k++) {
+ subtable->firstValues[j][k] = position_zero();
+ subtable->secondValues[j][k] = position_zero();
+ }
+ };
+ for (glyphclass_t j = 0; j <= subtable->first->maxclass; j++) {
+ uint32_t pairSetOffset = offset + read_16u(data + offset + 10 + 2 * j);
+ glyphid_t pairCount = read_16u(data + pairSetOffset);
+ for (glyphid_t k = 0; k < pairCount; k++) {
+ int second = read_16u(data + pairSetOffset + 2 + (2 + len1 + len2) * k);
+ pair_classifier_hash *s;
+ HASH_FIND_INT(h, &second, s);
+ if (s) {
+ subtable->firstValues[j][s->cid] = read_gpos_value(
+ data, tableLength, pairSetOffset + 2 + (2 + len1 + len2) * k + 2, format1);
+ subtable->secondValues[j][s->cid] = read_gpos_value(
+ data, tableLength, pairSetOffset + 2 + (2 + len1 + len2) * k + 2 + len1,
+ format2);
+ }
+ }
+ }
+ // pass 4: return
+ pair_classifier_hash *s, *tmp;
+ glyphid_t jj = 0;
+ HASH_ITER(hh, h, s, tmp) {
+ subtable->second->glyphs[jj] = Handle.fromIndex(s->gid);
+ subtable->second->classes[jj] = s->cid;
+ jj++;
+ HASH_DEL(h, s);
+ FREE(s);
+ }
+ return (otl_Subtable *)subtable;
+ } else if (subtableFormat == 2) {
+ // pair adjustment by classes
+ checkLength(offset + 16);
+ uint16_t format1 = read_16u(data + offset + 4);
+ uint16_t format2 = read_16u(data + offset + 6);
+ uint8_t len1 = position_format_length(format1);
+ uint8_t len2 = position_format_length(format2);
+ otl_Coverage *cov = Coverage.read(data, tableLength, offset + read_16u(data + offset + 2));
+ subtable->first = ClassDef.read(data, tableLength, offset + read_16u(data + offset + 8));
+ subtable->first = ClassDef.expand(cov, subtable->first);
+ DELETE(Coverage.free, cov);
+ subtable->second = ClassDef.read(data, tableLength, offset + read_16u(data + offset + 10));
+ if (!subtable->first || !subtable->second) goto FAIL;
+ glyphclass_t class1Count = read_16u(data + offset + 12);
+ glyphclass_t class2Count = read_16u(data + offset + 14);
+ if (subtable->first->maxclass + 1 != class1Count) goto FAIL;
+ if (subtable->second->maxclass + 1 != class2Count) goto FAIL;
+
+ checkLength(offset + 16 + class1Count * class2Count * (len1 + len2));
+ // read the matrix
+ NEW(subtable->firstValues, class1Count);
+ NEW(subtable->secondValues, class1Count);
+
+ for (glyphclass_t j = 0; j < class1Count; j++) {
+ NEW(subtable->firstValues[j], class2Count);
+ NEW(subtable->secondValues[j], class2Count);
+ for (glyphclass_t k = 0; k < class2Count; k++) {
+ subtable->firstValues[j][k] =
+ read_gpos_value(data, tableLength,
+ offset + 16 + (j * class2Count + k) * (len1 + len2), format1);
+ subtable->secondValues[j][k] = read_gpos_value(
+ data, tableLength, offset + 16 + (j * class2Count + k) * (len1 + len2) + len1,
+ format2);
+ }
+ }
+
+ return (otl_Subtable *)subtable;
+ }
+FAIL:
+ iSubtable_gpos_pair.free(subtable);
+ return NULL;
+}
+
+json_value *otl_gpos_dump_pair(const otl_Subtable *_subtable) {
+ const subtable_gpos_pair *subtable = &(_subtable->gpos_pair);
+ json_value *st = json_object_new(3);
+ json_object_push(st, "first", ClassDef.dump(subtable->first));
+ json_object_push(st, "second", ClassDef.dump(subtable->second));
+
+ json_value *mat = json_array_new(subtable->first->maxclass + 1);
+ for (glyphclass_t j = 0; j <= subtable->first->maxclass; j++) {
+ json_value *row = json_array_new(subtable->second->maxclass + 1);
+ for (glyphclass_t k = 0; k <= subtable->second->maxclass; k++) {
+ uint8_t f1 = required_position_format(subtable->firstValues[j][k]);
+ uint8_t f2 = required_position_format(subtable->secondValues[j][k]);
+ if (f1 | f2) {
+ if (f1 == FORMAT_DWIDTH && f2 == 0) {
+ // simple kerning
+ json_array_push(row, json_new_position(subtable->firstValues[j][k].dWidth));
+ } else {
+ json_value *pair = json_object_new(2);
+ if (f1) {
+ json_object_push(pair, "first",
+ gpos_dump_value(subtable->firstValues[j][k]));
+ }
+ if (f2) {
+ json_object_push(pair, "second",
+ gpos_dump_value(subtable->secondValues[j][k]));
+ }
+ json_array_push(row, pair);
+ }
+ } else {
+ json_array_push(row, json_new_position(0));
+ }
+ }
+ json_array_push(mat, preserialize(row));
+ }
+ json_object_push(st, "matrix", mat);
+ return st;
+}
+otl_Subtable *otl_gpos_parse_pair(const json_value *_subtable, const otfcc_Options *options) {
+ subtable_gpos_pair *subtable = iSubtable_gpos_pair.create();
+
+ json_value *_mat = json_obj_get_type(_subtable, "matrix", json_array);
+ subtable->first = ClassDef.parse(json_obj_get_type(_subtable, "first", json_object));
+ subtable->second = ClassDef.parse(json_obj_get_type(_subtable, "second", json_object));
+ if (!_mat || !subtable->first || !subtable->second) goto FAIL;
+
+ glyphclass_t class1Count = subtable->first->maxclass + 1;
+ glyphclass_t class2Count = subtable->second->maxclass + 1;
+
+ NEW(subtable->firstValues, class1Count);
+ NEW(subtable->secondValues, class1Count);
+
+ for (glyphclass_t j = 0; j < class1Count; j++) {
+ NEW(subtable->firstValues[j], class2Count);
+ NEW(subtable->secondValues[j], class2Count);
+ for (glyphclass_t k = 0; k < class2Count; k++) {
+ subtable->firstValues[j][k] = position_zero();
+ subtable->secondValues[j][k] = position_zero();
+ }
+ }
+ for (glyphclass_t j = 0; j < class1Count && j < _mat->u.array.length; j++) {
+ json_value *_row = _mat->u.array.values[j];
+ if (!_row || _row->type != json_array) continue;
+ for (glyphclass_t k = 0; k < class2Count && k < _row->u.array.length; k++) {
+ json_value *_item = _row->u.array.values[k];
+ if (_item->type == json_integer) {
+ subtable->firstValues[j][k].dWidth = _item->u.integer;
+ } else if (_item->type == json_double) {
+ subtable->firstValues[j][k].dWidth = _item->u.dbl;
+ } else if (_item->type == json_object) {
+ subtable->firstValues[j][k] = gpos_parse_value(json_obj_get(_item, "first"));
+ subtable->secondValues[j][k] = gpos_parse_value(json_obj_get(_item, "second"));
+ }
+ }
+ }
+ return (otl_Subtable *)subtable;
+FAIL:
+ iSubtable_gpos_pair.free(subtable);
+ return NULL;
+}
+static otl_Coverage *covFromCD(otl_ClassDef *cd) {
+ otl_Coverage *cov;
+ NEW(cov);
+ cov->numGlyphs = cd->numGlyphs;
+ NEW(cov->glyphs, cd->numGlyphs);
+ for (glyphid_t j = 0; j < cd->numGlyphs; j++) {
+ cov->glyphs[j] = Handle.dup(cd->glyphs[j]);
+ }
+ return cov;
+}
+
+typedef struct {
+ glyphid_t gid;
+ otl_PositionValue *fv;
+ otl_PositionValue *sv;
+} IndividualGposPair;
+
+static int by_pairSecondGlyph(const void *a, const void *b) {
+ return ((const IndividualGposPair *)a)->gid - ((const IndividualGposPair *)b)->gid;
+}
+
+static bk_Block *otfcc_build_gpos_pair_individual(const otl_Subtable *_subtable) {
+ const subtable_gpos_pair *subtable = &(_subtable->gpos_pair);
+ uint16_t format1 = 0;
+ uint16_t format2 = 0;
+ glyphclass_t class1Count = subtable->first->maxclass + 1;
+ glyphclass_t class2Count = subtable->second->maxclass + 1;
+
+ for (glyphclass_t j = 0; j < class1Count; j++) {
+ for (glyphclass_t k = 0; k < class2Count; k++) {
+ format1 |= required_position_format(subtable->firstValues[j][k]);
+ format2 |= required_position_format(subtable->secondValues[j][k]);
+ }
+ }
+ glyphid_t *pairCounts;
+ NEW(pairCounts, subtable->first->numGlyphs);
+ for (glyphid_t j = 0; j < subtable->first->numGlyphs; j++) {
+ pairCounts[j] = 0;
+ for (glyphid_t k = 0; k < subtable->second->numGlyphs; k++) {
+ glyphclass_t c1 = subtable->first->classes[j];
+ glyphclass_t c2 = subtable->second->classes[k];
+ if (required_position_format(subtable->firstValues[c1][c2]) |
+ required_position_format(subtable->secondValues[c1][c2])) {
+ pairCounts[j] += 1;
+ }
+ }
+ }
+ otl_Coverage *cov = covFromCD(subtable->first);
+ Coverage.shrink(cov, true);
+ bk_Block *root = bk_new_Block(b16, 1, // PosFormat
+ p16, bk_newBlockFromBuffer(Coverage.build(cov)), // Coverage
+ b16, format1, // ValueFormat1
+ b16, format2, // ValueFormat2
+ b16, subtable->first->numGlyphs, // PairSetCount
+ bkover);
+
+ for (glyphid_t j = 0; j < cov->numGlyphs; j++) {
+ tableid_t currentPairCount = 0;
+ glyphclass_t c1 = 0;
+ for (glyphid_t k = 0; k < subtable->first->numGlyphs; k++) {
+ if (subtable->first->glyphs[k].index == cov->glyphs[j].index) {
+ // The coverage is sorted, not direct correspondence with subtable->first.
+ c1 = subtable->first->classes[k];
+ currentPairCount = pairCounts[k];
+ }
+ }
+ bk_Block *pairSet = bk_new_Block(b16, currentPairCount, // PairValueCount
+ bkover);
+ IndividualGposPair *pairs;
+ NEW_CLEAN_N(pairs, currentPairCount);
+ size_t n = 0;
+ for (glyphid_t k = 0; k < subtable->second->numGlyphs; k++) {
+ glyphclass_t c2 = subtable->second->classes[k];
+ if (required_position_format(subtable->firstValues[c1][c2]) |
+ required_position_format(subtable->secondValues[c1][c2])) {
+ pairs[n].gid = subtable->second->glyphs[k].index;
+ pairs[n].fv = &subtable->firstValues[c1][c2];
+ pairs[n].sv = &subtable->secondValues[c1][c2];
+ n++;
+ }
+ }
+ qsort(pairs, currentPairCount, sizeof(IndividualGposPair), by_pairSecondGlyph);
+ for (size_t n = 0; n < currentPairCount; n++) {
+ bk_push(pairSet, b16, pairs[n].gid, // SecondGlyph
+ bkembed, bk_gpos_value(*(pairs[n].fv), format1), // Value1
+ bkembed, bk_gpos_value(*(pairs[n].sv), format2), // Value2
+ bkover);
+ }
+ FREE(pairs);
+ bk_push(root, p16, pairSet, bkover);
+ }
+ DELETE(Coverage.free, cov);
+ FREE(pairCounts);
+ return root;
+}
+static bk_Block *otfcc_build_gpos_pair_classes(const otl_Subtable *_subtable) {
+ const subtable_gpos_pair *subtable = &(_subtable->gpos_pair);
+ uint16_t format1 = 0;
+ uint16_t format2 = 0;
+ glyphclass_t class1Count = subtable->first->maxclass + 1;
+ glyphclass_t class2Count = subtable->second->maxclass + 1;
+
+ for (glyphclass_t j = 0; j < class1Count; j++) {
+ for (glyphclass_t k = 0; k < class2Count; k++) {
+ format1 |= required_position_format(subtable->firstValues[j][k]);
+ format2 |= required_position_format(subtable->secondValues[j][k]);
+ }
+ }
+ otl_Coverage *cov = covFromCD(subtable->first);
+ bk_Block *root =
+ bk_new_Block(b16, 2, // PosFormat
+ p16, bk_newBlockFromBuffer(Coverage.build(cov)), // Coverage
+ b16, format1, // ValueFormat1
+ b16, format2, // ValueFormat2
+ p16, bk_newBlockFromBuffer(ClassDef.build(subtable->first)), // ClassDef1
+ p16, bk_newBlockFromBuffer(ClassDef.build(subtable->second)), // ClassDef2
+ b16, class1Count, // Class1Count
+ b16, class2Count, // Class2Count
+ bkover);
+ for (glyphclass_t j = 0; j < class1Count; j++) {
+ for (glyphclass_t k = 0; k < class2Count; k++) {
+ bk_push(root, bkembed, bk_gpos_value(subtable->firstValues[j][k], format1), // Value1
+ bkembed, bk_gpos_value(subtable->secondValues[j][k], format2), // Value2
+ bkover);
+ }
+ }
+ DELETE(Coverage.free, cov);
+ return root;
+}
+caryll_Buffer *otfcc_build_gpos_pair(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics) {
+ bk_Block *format1 = otfcc_build_gpos_pair_individual(_subtable);
+ bk_Block *format2 = otfcc_build_gpos_pair_classes(_subtable);
+ bk_Graph *g1 = bk_newGraphFromRootBlock(format1);
+ bk_Graph *g2 = bk_newGraphFromRootBlock(format2);
+ bk_minimizeGraph(g1);
+ bk_minimizeGraph(g2);
+ if (bk_estimateSizeOfGraph(g1) > bk_estimateSizeOfGraph(g2)) {
+ // Choose pair adjustment by classes
+ bk_delete_Graph(g1);
+ bk_untangleGraph(g2);
+ caryll_Buffer *buf = bk_build_Graph(g2);
+ bk_delete_Graph(g2);
+ return buf;
+ } else {
+ // Choose pair adjustment by individuals
+ bk_delete_Graph(g2);
+ bk_untangleGraph(g1);
+ caryll_Buffer *buf = bk_build_Graph(g1);
+ bk_delete_Graph(g1);
+ return buf;
+ }
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-pair.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-pair.h
new file mode 100644
index 00000000000..ce09c209b81
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-pair.h
@@ -0,0 +1,13 @@
+#ifndef CARYLL_TABLE_OTL_GPOS_PAIR_H
+#define CARYLL_TABLE_OTL_GPOS_PAIR_H
+
+#include "common.h"
+
+otl_Subtable *otl_read_gpos_pair(const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options);
+json_value *otl_gpos_dump_pair(const otl_Subtable *_subtable);
+otl_Subtable *otl_gpos_parse_pair(const json_value *_subtable, const otfcc_Options *options);
+caryll_Buffer *otfcc_build_gpos_pair(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics);
+
+#endif
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-single.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-single.c
new file mode 100644
index 00000000000..d1f42faa94c
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-single.c
@@ -0,0 +1,129 @@
+#include "gpos-single.h"
+#include "gpos-common.h"
+
+static void deleteGposSingleEntry(otl_GposSingleEntry *entry) {
+ Handle.dispose(&entry->target);
+}
+
+static caryll_ElementInterface(otl_GposSingleEntry) gss_typeinfo = {
+ .init = NULL, .copy = NULL, .dispose = deleteGposSingleEntry};
+
+caryll_standardVectorImpl(subtable_gpos_single, otl_GposSingleEntry, gss_typeinfo,
+ iSubtable_gpos_single);
+
+otl_Subtable *otl_read_gpos_single(const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options) {
+ subtable_gpos_single *subtable = iSubtable_gpos_single.create();
+ otl_Coverage *targets = NULL;
+
+ checkLength(offset + 6);
+
+ uint16_t subtableFormat = read_16u(data + offset);
+ targets = Coverage.read(data, tableLength, offset + read_16u(data + offset + 2));
+ if (!targets || targets->numGlyphs == 0) goto FAIL;
+
+ if (subtableFormat == 1) {
+ otl_PositionValue v =
+ read_gpos_value(data, tableLength, offset + 6, read_16u(data + offset + 4));
+ for (glyphid_t j = 0; j < targets->numGlyphs; j++) {
+ iSubtable_gpos_single.push(subtable,
+ ((otl_GposSingleEntry){
+ .target = Handle.dup(targets->glyphs[j]), .value = v,
+ }));
+ }
+ } else {
+ uint16_t valueFormat = read_16u(data + offset + 4);
+ uint16_t valueCount = read_16u(data + offset + 6);
+ checkLength(offset + 8 + position_format_length(valueFormat) * valueCount);
+ if (valueCount != targets->numGlyphs) goto FAIL;
+
+ for (glyphid_t j = 0; j < targets->numGlyphs; j++) {
+ iSubtable_gpos_single.push(
+ subtable,
+ ((otl_GposSingleEntry){
+ .target = Handle.dup(targets->glyphs[j]),
+ .value = read_gpos_value(data, tableLength,
+ offset + 8 + j * position_format_length(valueFormat),
+ valueFormat),
+ }));
+ }
+ }
+ if (targets) Coverage.free(targets);
+ return (otl_Subtable *)subtable;
+
+FAIL:
+ if (targets) Coverage.free(targets);
+ iSubtable_gpos_single.free(subtable);
+ return NULL;
+}
+
+json_value *otl_gpos_dump_single(const otl_Subtable *_subtable) {
+ const subtable_gpos_single *subtable = &(_subtable->gpos_single);
+ json_value *st = json_object_new(subtable->length);
+ for (glyphid_t j = 0; j < subtable->length; j++) {
+ json_object_push(st, subtable->items[j].target.name,
+ gpos_dump_value(subtable->items[j].value));
+ }
+ return st;
+}
+otl_Subtable *otl_gpos_parse_single(const json_value *_subtable, const otfcc_Options *options) {
+ subtable_gpos_single *subtable = iSubtable_gpos_single.create();
+ for (glyphid_t j = 0; j < _subtable->u.object.length; j++) {
+ if (_subtable->u.object.values[j].value &&
+ _subtable->u.object.values[j].value->type == json_object) {
+ sds gname = sdsnewlen(_subtable->u.object.values[j].name,
+ _subtable->u.object.values[j].name_length);
+ iSubtable_gpos_single.push(
+ subtable, ((otl_GposSingleEntry){
+ .target = Handle.fromName(gname),
+ .value = gpos_parse_value(_subtable->u.object.values[j].value),
+ }));
+ }
+ }
+ return (otl_Subtable *)subtable;
+}
+
+caryll_Buffer *otfcc_build_gpos_single(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics) {
+ const subtable_gpos_single *subtable = &(_subtable->gpos_single);
+ bool isConst = subtable->length > 0;
+ uint16_t format = 0;
+ if (subtable->length > 0) {
+ for (glyphid_t j = 0; j < subtable->length; j++) {
+ isConst = isConst && (subtable->items[j].value.dx == subtable->items[0].value.dx) &&
+ (subtable->items[j].value.dy == subtable->items[0].value.dy) &&
+ (subtable->items[j].value.dWidth == subtable->items[0].value.dWidth) &&
+ (subtable->items[j].value.dHeight == subtable->items[0].value.dHeight);
+ format |= required_position_format(subtable->items[j].value);
+ }
+ }
+ otl_Coverage *cov = Coverage.create();
+ for (glyphid_t j = 0; j < subtable->length; j++) {
+ Coverage.push(cov, Handle.dup(subtable->items[j].target));
+ }
+
+ caryll_Buffer *coverageBuf = Coverage.build(cov);
+
+ if (isConst) {
+ bk_Block *b =
+ (bk_new_Block(b16, 1, // Format
+ p16, bk_newBlockFromBuffer(coverageBuf), // coverage
+ b16, format, // format
+ bkembed, bk_gpos_value(subtable->items[0].value, format), // value
+ bkover));
+ Coverage.free(cov);
+ return bk_build_Block(b);
+ } else {
+ bk_Block *b = bk_new_Block(b16, 2, // Format
+ p16, bk_newBlockFromBuffer(coverageBuf), // coverage
+ b16, format, // format
+ b16, subtable->length, // quantity
+ bkover);
+ for (glyphid_t k = 0; k < subtable->length; k++) {
+ bk_push(b, bkembed, bk_gpos_value(subtable->items[k].value, format), // value
+ bkover);
+ }
+ Coverage.free(cov);
+ return bk_build_Block(b);
+ }
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-single.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-single.h
new file mode 100644
index 00000000000..20774d72ac0
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gpos-single.h
@@ -0,0 +1,13 @@
+#ifndef CARYLL_TABLE_OTL_GPOS_SINGLE_H
+#define CARYLL_TABLE_OTL_GPOS_SINGLE_H
+
+#include "common.h"
+
+otl_Subtable *otl_read_gpos_single(const font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options);
+json_value *otl_gpos_dump_single(const otl_Subtable *_subtable);
+otl_Subtable *otl_gpos_parse_single(const json_value *_subtable, const otfcc_Options *options);
+caryll_Buffer *otfcc_build_gpos_single(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics);
+
+#endif
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-ligature.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-ligature.c
new file mode 100644
index 00000000000..49f126fd47f
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-ligature.c
@@ -0,0 +1,177 @@
+#include "gsub-ligature.h"
+
+static void deleteGsubLigatureEntry(otl_GsubLigatureEntry *entry) {
+ Handle.dispose(&entry->to);
+ DELETE(Coverage.free, entry->from);
+}
+static caryll_ElementInterface(otl_GsubLigatureEntry) gss_typeinfo = {
+ .init = NULL, .copy = NULL, .dispose = deleteGsubLigatureEntry};
+
+caryll_standardVectorImpl(subtable_gsub_ligature, otl_GsubLigatureEntry, gss_typeinfo,
+ iSubtable_gsub_ligature);
+
+otl_Subtable *otl_read_gsub_ligature(const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options) {
+ subtable_gsub_ligature *subtable = iSubtable_gsub_ligature.create();
+ checkLength(offset + 6);
+
+ otl_Coverage *startCoverage =
+ Coverage.read(data, tableLength, offset + read_16u(data + offset + 2));
+ if (!startCoverage) goto FAIL;
+ glyphid_t setCount = read_16u(data + offset + 4);
+ if (setCount != startCoverage->numGlyphs) goto FAIL;
+ checkLength(offset + 6 + setCount * 2);
+
+ uint32_t ligatureCount = 0;
+ for (glyphid_t j = 0; j < setCount; j++) {
+ uint32_t setOffset = offset + read_16u(data + offset + 6 + j * 2);
+ checkLength(setOffset + 2);
+ ligatureCount += read_16u(data + setOffset);
+ checkLength(setOffset + 2 + read_16u(data + setOffset) * 2);
+ }
+
+ for (glyphid_t j = 0; j < setCount; j++) {
+ uint32_t setOffset = offset + read_16u(data + offset + 6 + j * 2);
+ glyphid_t lc = read_16u(data + setOffset);
+ for (glyphid_t k = 0; k < lc; k++) {
+ uint32_t ligOffset = setOffset + read_16u(data + setOffset + 2 + k * 2);
+ checkLength(ligOffset + 4);
+ glyphid_t ligComponents = read_16u(data + ligOffset + 2);
+ checkLength(ligOffset + 2 + ligComponents * 2);
+
+ otl_Coverage *cov = Coverage.create();
+ Coverage.push(cov, Handle.fromIndex(startCoverage->glyphs[j].index));
+ for (glyphid_t m = 1; m < ligComponents; m++) {
+ Coverage.push(cov, Handle.fromIndex(read_16u(data + ligOffset + 2 + m * 2)));
+ }
+ iSubtable_gsub_ligature.push(
+ subtable, ((otl_GsubLigatureEntry){
+ .from = cov, .to = Handle.fromIndex(read_16u(data + ligOffset)),
+ }));
+ }
+ }
+ Coverage.free(startCoverage);
+ return (otl_Subtable *)subtable;
+FAIL:
+ iSubtable_gsub_ligature.free(subtable);
+ return NULL;
+}
+
+json_value *otl_gsub_dump_ligature(const otl_Subtable *_subtable) {
+ const subtable_gsub_ligature *subtable = &(_subtable->gsub_ligature);
+ json_value *st = json_array_new(subtable->length);
+ for (glyphid_t j = 0; j < subtable->length; j++) {
+ json_value *entry = json_object_new(2);
+ json_object_push(entry, "from", Coverage.dump(subtable->items[j].from));
+ json_object_push(entry, "to",
+ json_string_new_length((uint32_t)sdslen(subtable->items[j].to.name),
+ subtable->items[j].to.name));
+ json_array_push(st, preserialize(entry));
+ }
+ json_value *ret = json_object_new(1);
+ json_object_push(ret, "substitutions", st);
+ return ret;
+}
+
+otl_Subtable *otl_gsub_parse_ligature(const json_value *_subtable, const otfcc_Options *options) {
+ if (json_obj_get_type(_subtable, "substitutions", json_array)) {
+ _subtable = json_obj_get_type(_subtable, "substitutions", json_array);
+
+ subtable_gsub_ligature *st = iSubtable_gsub_ligature.create();
+ glyphid_t n = _subtable->u.array.length;
+
+ for (glyphid_t k = 0; k < n; k++) {
+ json_value *entry = _subtable->u.array.values[k];
+ json_value *_from = json_obj_get_type(entry, "from", json_array);
+ json_value *_to = json_obj_get_type(entry, "to", json_string);
+ if (!_from || !_to) continue;
+ iSubtable_gsub_ligature.push(
+ st, ((otl_GsubLigatureEntry){
+ .to = Handle.fromName(sdsnewlen(_to->u.string.ptr, _to->u.string.length)),
+ .from = Coverage.parse(_from),
+ }));
+ }
+ return (otl_Subtable *)st;
+ } else {
+ subtable_gsub_ligature *st = iSubtable_gsub_ligature.create();
+ glyphid_t n = _subtable->u.array.length;
+
+ for (glyphid_t k = 0; k < n; k++) {
+ json_value *_from = _subtable->u.object.values[k].value;
+ if (!_from || _from->type != json_array) continue;
+ iSubtable_gsub_ligature.push(
+ st, ((otl_GsubLigatureEntry){
+ .to = Handle.fromName(sdsnewlen(_subtable->u.object.values[k].name,
+ _subtable->u.object.values[k].name_length)),
+ .from = Coverage.parse(_from),
+ }));
+ }
+ return (otl_Subtable *)st;
+ }
+ return NULL;
+}
+
+typedef struct {
+ int gid;
+ UT_hash_handle hh;
+} ligature_aggerator;
+static int by_gid(ligature_aggerator *a, ligature_aggerator *b) {
+ return a->gid - b->gid;
+}
+
+caryll_Buffer *otfcc_build_gsub_ligature_subtable(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics) {
+ const subtable_gsub_ligature *subtable = &(_subtable->gsub_ligature);
+
+ ligature_aggerator *h = NULL, *s, *tmp;
+ glyphid_t nLigatures = subtable->length;
+ for (glyphid_t j = 0; j < nLigatures; j++) {
+ int sgid = subtable->items[j].from->glyphs[0].index;
+ HASH_FIND_INT(h, &sgid, s);
+ if (!s) {
+ NEW(s);
+ s->gid = sgid;
+ HASH_ADD_INT(h, gid, s);
+ }
+ }
+ HASH_SORT(h, by_gid);
+
+ otl_Coverage *startcov = Coverage.create();
+
+ foreach_hash(s, h) {
+ Coverage.push(startcov, Handle.fromIndex(s->gid));
+ }
+
+ bk_Block *root = bk_new_Block(b16, 1, // format
+ p16, bk_newBlockFromBuffer(Coverage.build(startcov)), // coverage
+ b16, startcov->numGlyphs, // LigSetCount
+ bkover);
+
+ foreach_hash(s, h) {
+ glyphid_t nLigsHere = 0;
+ for (glyphid_t j = 0; j < nLigatures; j++)
+ if (subtable->items[j].from->glyphs[0].index == s->gid) nLigsHere++;
+ bk_Block *ligset = bk_new_Block(b16, nLigsHere, bkover);
+
+ for (glyphid_t j = 0; j < nLigatures; j++) {
+ if (subtable->items[j].from->glyphs[0].index == s->gid) {
+ bk_Block *ligdef =
+ bk_new_Block(b16, subtable->items[j].to.index, // ligGlyph
+ b16, subtable->items[j].from->numGlyphs, // compCount
+ bkover);
+ for (glyphid_t m = 1; m < subtable->items[j].from->numGlyphs; m++) {
+ bk_push(ligdef, b16, subtable->items[j].from->glyphs[m].index, bkover);
+ }
+ bk_push(ligset, p16, ligdef, bkover);
+ }
+ }
+ bk_push(root, p16, ligset, bkover);
+ }
+
+ Coverage.free(startcov);
+ HASH_ITER(hh, h, s, tmp) {
+ HASH_DEL(h, s);
+ FREE(s);
+ }
+ return bk_build_Block(root);
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-ligature.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-ligature.h
new file mode 100644
index 00000000000..f76244c5495
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-ligature.h
@@ -0,0 +1,13 @@
+#ifndef CARYLL_TABLE_OTL_GSUB_LIGATURE_H
+#define CARYLL_TABLE_OTL_GSUB_LIGATURE_H
+
+#include "common.h"
+
+otl_Subtable *otl_read_gsub_ligature(const font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options);
+json_value *otl_gsub_dump_ligature(const otl_Subtable *_subtable);
+otl_Subtable *otl_gsub_parse_ligature(const json_value *_subtable, const otfcc_Options *options);
+caryll_Buffer *otfcc_build_gsub_ligature_subtable(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics);
+
+#endif
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-multi.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-multi.c
new file mode 100644
index 00000000000..938218b3c4c
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-multi.c
@@ -0,0 +1,91 @@
+#include "gsub-multi.h"
+
+static void deleteGsubMultiEntry(otl_GsubMultiEntry *entry) {
+ Handle.dispose(&entry->from);
+ DELETE(Coverage.free, entry->to);
+}
+
+static caryll_ElementInterface(otl_GsubMultiEntry) gsm_typeinfo = {
+ .init = NULL, .copy = NULL, .dispose = deleteGsubMultiEntry};
+
+caryll_standardVectorImpl(subtable_gsub_multi, otl_GsubMultiEntry, gsm_typeinfo,
+ iSubtable_gsub_multi);
+
+otl_Subtable *otl_read_gsub_multi(font_file_pointer data, uint32_t tableLength, uint32_t offset,
+ const glyphid_t maxGlyphs, const otfcc_Options *options) {
+ subtable_gsub_multi *subtable = iSubtable_gsub_multi.create();
+ otl_Coverage *from = NULL;
+ checkLength(offset + 6);
+
+ from = Coverage.read(data, tableLength, offset + read_16u(data + offset + 2));
+ glyphid_t seqCount = read_16u(data + offset + 4);
+ if (seqCount != from->numGlyphs) goto FAIL;
+ checkLength(offset + 6 + seqCount * 2);
+
+ for (glyphid_t j = 0; j < seqCount; j++) {
+ uint32_t seqOffset = offset + read_16u(data + offset + 6 + j * 2);
+ otl_Coverage *cov = Coverage.create();
+ glyphid_t n = read_16u(data + seqOffset);
+ for (glyphid_t k = 0; k < n; k++) {
+ Coverage.push(cov, Handle.fromIndex(read_16u(data + seqOffset + 2 + k * 2)));
+ }
+ iSubtable_gsub_multi.push(subtable, ((otl_GsubMultiEntry){
+ .from = Handle.dup(from->glyphs[j]), .to = cov,
+ }));
+ }
+ Coverage.free(from);
+ return (otl_Subtable *)subtable;
+
+FAIL:
+ if (from) Coverage.free(from);
+ iSubtable_gsub_multi.free(subtable);
+ return NULL;
+}
+
+json_value *otl_gsub_dump_multi(const otl_Subtable *_subtable) {
+ const subtable_gsub_multi *subtable = &(_subtable->gsub_multi);
+ json_value *st = json_object_new(subtable->length);
+ for (glyphid_t j = 0; j < subtable->length; j++) {
+ json_object_push(st, subtable->items[j].from.name, Coverage.dump(subtable->items[j].to));
+ }
+ return st;
+}
+
+otl_Subtable *otl_gsub_parse_multi(const json_value *_subtable, const otfcc_Options *options) {
+ subtable_gsub_multi *st = iSubtable_gsub_multi.create();
+
+ for (glyphid_t k = 0; k < _subtable->u.object.length; k++) {
+ json_value *_to = _subtable->u.object.values[k].value;
+ if (!_to || _to->type != json_array) continue;
+ iSubtable_gsub_multi.push(
+ st, ((otl_GsubMultiEntry){
+ .from = Handle.fromName(sdsnewlen(_subtable->u.object.values[k].name,
+ _subtable->u.object.values[k].name_length)),
+ .to = Coverage.parse(_to),
+ }));
+ }
+
+ return (otl_Subtable *)st;
+}
+
+caryll_Buffer *otfcc_build_gsub_multi_subtable(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics) {
+ const subtable_gsub_multi *subtable = &(_subtable->gsub_multi);
+ otl_Coverage *cov = Coverage.create();
+ for (glyphid_t j = 0; j < subtable->length; j++) {
+ Coverage.push(cov, Handle.dup(subtable->items[j].from));
+ }
+
+ bk_Block *root = bk_new_Block(b16, 1, // format
+ p16, bk_newBlockFromBuffer(Coverage.build(cov)), // coverage
+ b16, subtable->length, // quantity
+ bkover);
+ for (glyphid_t j = 0; j < subtable->length; j++) {
+ bk_Block *b = bk_new_Block(b16, subtable->items[j].to->numGlyphs, bkover);
+ for (glyphid_t k = 0; k < subtable->items[j].to->numGlyphs; k++) {
+ bk_push(b, b16, subtable->items[j].to->glyphs[k].index, bkover);
+ }
+ bk_push(root, p16, b, bkover);
+ }
+ Coverage.free(cov);
+ return bk_build_Block(root);
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-multi.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-multi.h
new file mode 100644
index 00000000000..bfd4eb7dc5d
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-multi.h
@@ -0,0 +1,13 @@
+#ifndef CARYLL_TABLE_OTL_GSUB_MULTI_H
+#define CARYLL_TABLE_OTL_GSUB_MULTI_H
+
+#include "common.h"
+
+otl_Subtable *otl_read_gsub_multi(const font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options);
+json_value *otl_gsub_dump_multi(const otl_Subtable *_subtable);
+otl_Subtable *otl_gsub_parse_multi(const json_value *_subtable, const otfcc_Options *options);
+caryll_Buffer *otfcc_build_gsub_multi_subtable(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics);
+
+#endif
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-reverse.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-reverse.c
new file mode 100644
index 00000000000..023e3d3d6b3
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-reverse.c
@@ -0,0 +1,133 @@
+#include "gsub-reverse.h"
+
+static INLINE void initGsubReverse(subtable_gsub_reverse *subtable) {
+ subtable->match = NULL;
+ subtable->to = NULL;
+}
+static INLINE void disposeGsubReverse(subtable_gsub_reverse *subtable) {
+ if (subtable->match)
+ for (tableid_t j = 0; j < subtable->matchCount; j++) {
+ Coverage.free(subtable->match[j]);
+ }
+ if (subtable->to) Coverage.free(subtable->to);
+}
+
+caryll_standardRefType(subtable_gsub_reverse, iSubtable_gsub_reverse, initGsubReverse,
+ disposeGsubReverse);
+
+static void reverseBacktracks(otl_Coverage **match, tableid_t inputIndex) {
+ if (inputIndex > 0) {
+ tableid_t start = 0;
+ tableid_t end = inputIndex - 1;
+ while (end > start) {
+ otl_Coverage *tmp = match[start];
+ match[start] = match[end];
+ match[end] = tmp;
+ end--, start++;
+ }
+ }
+}
+
+otl_Subtable *otl_read_gsub_reverse(const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options) {
+ subtable_gsub_reverse *subtable = iSubtable_gsub_reverse.create();
+ checkLength(offset + 6);
+
+ tableid_t nBacktrack = read_16u(data + offset + 4);
+ checkLength(offset + 6 + nBacktrack * 2);
+
+ tableid_t nForward = read_16u(data + offset + 6 + nBacktrack * 2);
+ checkLength(offset + 8 + (nBacktrack + nForward) * 2);
+
+ tableid_t nReplacement = read_16u(data + offset + 8 + (nBacktrack + nForward) * 2);
+ checkLength(offset + 10 + (nBacktrack + nForward + nReplacement) * 2);
+
+ subtable->matchCount = nBacktrack + nForward + 1;
+ NEW(subtable->match, subtable->matchCount);
+ subtable->inputIndex = nBacktrack;
+
+ for (tableid_t j = 0; j < nBacktrack; j++) {
+ uint32_t covOffset = offset + read_16u(data + offset + 6 + j * 2);
+ subtable->match[j] = Coverage.read(data, tableLength, covOffset);
+ }
+ {
+ uint32_t covOffset = offset + read_16u(data + offset + 2);
+ subtable->match[subtable->inputIndex] = Coverage.read(data, tableLength, covOffset);
+ if (nReplacement != subtable->match[subtable->inputIndex]->numGlyphs) goto FAIL;
+ }
+ for (tableid_t j = 0; j < nForward; j++) {
+ uint32_t covOffset = offset + read_16u(data + offset + 8 + nBacktrack * 2 + j * 2);
+ subtable->match[nBacktrack + 1 + j] = Coverage.read(data, tableLength, covOffset);
+ }
+
+ NEW(subtable->to);
+ subtable->to->numGlyphs = nReplacement;
+ NEW(subtable->to->glyphs, nReplacement);
+ for (tableid_t j = 0; j < nReplacement; j++) {
+ subtable->to->glyphs[j] =
+ Handle.fromIndex(read_16u(data + offset + 10 + (nBacktrack + nForward + j) * 2));
+ }
+ reverseBacktracks(subtable->match, subtable->inputIndex);
+ return (otl_Subtable *)subtable;
+
+FAIL:
+ iSubtable_gsub_reverse.free(subtable);
+ return NULL;
+}
+
+json_value *otl_gsub_dump_reverse(const otl_Subtable *_subtable) {
+ const subtable_gsub_reverse *subtable = &(_subtable->gsub_reverse);
+ json_value *_st = json_object_new(3);
+ json_value *_match = json_array_new(subtable->matchCount);
+ for (tableid_t j = 0; j < subtable->matchCount; j++) {
+ json_array_push(_match, Coverage.dump(subtable->match[j]));
+ }
+ json_object_push(_st, "match", _match);
+ json_object_push(_st, "to", Coverage.dump(subtable->to));
+ json_object_push(_st, "inputIndex", json_integer_new(subtable->inputIndex));
+ return _st;
+}
+
+otl_Subtable *otl_gsub_parse_reverse(const json_value *_subtable, const otfcc_Options *options) {
+ json_value *_match = json_obj_get_type(_subtable, "match", json_array);
+ json_value *_to = json_obj_get_type(_subtable, "to", json_array);
+ if (!_match || !_to) return NULL;
+
+ subtable_gsub_reverse *subtable = iSubtable_gsub_reverse.create();
+
+ subtable->matchCount = _match->u.array.length;
+ NEW(subtable->match, subtable->matchCount);
+
+ subtable->inputIndex = json_obj_getnum_fallback(_subtable, "inputIndex", 0);
+
+ for (tableid_t j = 0; j < subtable->matchCount; j++) {
+ subtable->match[j] = Coverage.parse(_match->u.array.values[j]);
+ }
+ subtable->to = Coverage.parse(_to);
+ return (otl_Subtable *)subtable;
+}
+
+caryll_Buffer *otfcc_build_gsub_reverse(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics) {
+ const subtable_gsub_reverse *subtable = &(_subtable->gsub_reverse);
+ reverseBacktracks(subtable->match, subtable->inputIndex);
+
+ bk_Block *root = bk_new_Block(b16, 1, // format
+ p16, bk_newBlockFromBuffer(Coverage.build(
+ subtable->match[subtable->inputIndex])), // coverage
+ bkover);
+ bk_push(root, b16, subtable->inputIndex, bkover);
+ for (tableid_t j = 0; j < subtable->inputIndex; j++) {
+ bk_push(root, p16, bk_newBlockFromBuffer(Coverage.build(subtable->match[j])), bkover);
+ }
+ bk_push(root, b16, subtable->matchCount - subtable->inputIndex - 1, bkover);
+ for (tableid_t j = subtable->inputIndex + 1; j < subtable->matchCount; j++) {
+ bk_push(root, p16, bk_newBlockFromBuffer(Coverage.build(subtable->match[j])), bkover);
+ }
+ bk_push(root, b16, subtable->to->numGlyphs, bkover);
+ for (tableid_t j = 0; j < subtable->to->numGlyphs; j++) {
+ bk_push(root, b16, subtable->to->glyphs[j].index, bkover);
+ }
+
+ return bk_build_Block(root);
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-reverse.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-reverse.h
new file mode 100644
index 00000000000..001b2a0cabc
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-reverse.h
@@ -0,0 +1,13 @@
+#ifndef CARYLL_TABLE_OTL_GSUB_REVERSE_H
+#define CARYLL_TABLE_OTL_GSUB_REVERSE_H
+
+#include "common.h"
+
+otl_Subtable *otl_read_gsub_reverse(const font_file_pointer data, uint32_t tableLength,
+ uint32_t offset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options);
+json_value *otl_gsub_dump_reverse(const otl_Subtable *_subtable);
+otl_Subtable *otl_gsub_parse_reverse(const json_value *_subtable, const otfcc_Options *options);
+caryll_Buffer *otfcc_build_gsub_reverse(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics);
+
+#endif
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-single.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-single.c
new file mode 100644
index 00000000000..fa1ac665ade
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-single.c
@@ -0,0 +1,139 @@
+#include "gsub-single.h"
+
+static void gss_entry_ctor(MODIFY otl_GsubSingleEntry *entry) {
+ entry->from = Handle.empty();
+ entry->to = Handle.empty();
+}
+static void gss_entry_copyctor(MODIFY otl_GsubSingleEntry *dst,
+ COPY const otl_GsubSingleEntry *src) {
+ dst->from = Handle.dup(src->from);
+ dst->to = Handle.dup(src->to);
+}
+static void gss_entry_dtor(MODIFY otl_GsubSingleEntry *entry) {
+ Handle.dispose(&entry->from);
+ Handle.dispose(&entry->to);
+}
+
+static caryll_ElementInterface(otl_GsubSingleEntry) gss_typeinfo = {
+ .init = gss_entry_ctor, .copy = gss_entry_copyctor, .dispose = gss_entry_dtor};
+
+caryll_standardVectorImpl(subtable_gsub_single, otl_GsubSingleEntry, gss_typeinfo,
+ iSubtable_gsub_single);
+
+otl_Subtable *otl_read_gsub_single(const font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options) {
+ subtable_gsub_single *subtable = iSubtable_gsub_single.create();
+ otl_Coverage *from = NULL;
+ otl_Coverage *to = NULL;
+ if (tableLength < subtableOffset + 6) goto FAIL;
+
+ uint16_t subtableFormat = read_16u(data + subtableOffset);
+ from = Coverage.read(data, tableLength, subtableOffset + read_16u(data + subtableOffset + 2));
+ if (!from || from->numGlyphs == 0) goto FAIL;
+
+ if (subtableFormat == 1) {
+ NEW(to);
+ to->numGlyphs = from->numGlyphs;
+ NEW(to->glyphs, to->numGlyphs);
+
+ uint16_t delta = read_16u(data + subtableOffset + 4);
+ for (glyphid_t j = 0; j < from->numGlyphs; j++) {
+ to->glyphs[j] = Handle.fromIndex(from->glyphs[j].index + delta);
+ }
+ } else {
+ glyphid_t toglyphs = read_16u(data + subtableOffset + 4);
+ if (tableLength < subtableOffset + 6 + toglyphs * 2 || toglyphs != from->numGlyphs)
+ goto FAIL;
+ NEW(to);
+ to->numGlyphs = toglyphs;
+ NEW(to->glyphs, to->numGlyphs);
+
+ for (glyphid_t j = 0; j < to->numGlyphs; j++) {
+ to->glyphs[j] = Handle.fromIndex(read_16u(data + subtableOffset + 6 + j * 2));
+ }
+ }
+ goto OK;
+FAIL:
+ iSubtable_gsub_single.free(subtable);
+ if (from) Coverage.free(from);
+ if (to) Coverage.free(to);
+ return NULL;
+OK:
+ for (glyphid_t j = 0; j < from->numGlyphs; j++) {
+ iSubtable_gsub_single.push(subtable, ((otl_GsubSingleEntry){
+ .from = Handle.dup(from->glyphs[j]), // from
+ .to = Handle.dup(to->glyphs[j]), // to
+ }));
+ }
+ if (from) Coverage.free(from);
+ if (to) Coverage.free(to);
+ return (otl_Subtable *)subtable;
+}
+
+json_value *otl_gsub_dump_single(const otl_Subtable *_subtable) {
+ const subtable_gsub_single *subtable = &(_subtable->gsub_single);
+ json_value *st = json_object_new(subtable->length);
+ for (size_t j = 0; j < subtable->length; j++) {
+ json_object_push(st, subtable->items[j].from.name,
+ json_string_new(subtable->items[j].to.name));
+ }
+ return st;
+}
+
+otl_Subtable *otl_gsub_parse_single(const json_value *_subtable, const otfcc_Options *options) {
+ subtable_gsub_single *subtable = iSubtable_gsub_single.create();
+ for (glyphid_t j = 0; j < _subtable->u.object.length; j++) {
+ if (_subtable->u.object.values[j].value &&
+ _subtable->u.object.values[j].value->type == json_string) {
+ glyph_handle from = Handle.fromName(sdsnewlen(
+ _subtable->u.object.values[j].name, _subtable->u.object.values[j].name_length));
+ glyph_handle to =
+ Handle.fromName(sdsnewlen(_subtable->u.object.values[j].value->u.string.ptr,
+ _subtable->u.object.values[j].value->u.string.length));
+ iSubtable_gsub_single.push(subtable, ((otl_GsubSingleEntry){.from = from, .to = to}));
+ }
+ }
+ return (otl_Subtable *)subtable;
+};
+
+caryll_Buffer *otfcc_build_gsub_single_subtable(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics) {
+ const subtable_gsub_single *subtable = &(_subtable->gsub_single);
+ bool isConstantDifference = subtable->length > 0;
+ if (isConstantDifference) {
+ int32_t difference = subtable->items[0].to.index - subtable->items[0].from.index;
+ isConstantDifference = isConstantDifference && difference < 0x8000 && difference > -0x8000;
+ for (glyphid_t j = 1; j < subtable->length; j++) {
+ int32_t diffJ = subtable->items[j].to.index - subtable->items[j].from.index;
+ isConstantDifference = isConstantDifference && diffJ == difference
+ && diffJ < 0x8000 && diffJ > -0x8000;
+ }
+ }
+ otl_Coverage *cov = Coverage.create();
+ for (glyphid_t j = 0; j < subtable->length; j++) {
+ Coverage.push(cov, Handle.dup(subtable->items[j].from));
+ }
+
+ caryll_Buffer *coverageBuf = Coverage.buildFormat(cov, heuristics & OTL_BH_GSUB_VERT ? 1 : 0);
+
+ if (isConstantDifference && !(heuristics & OTL_BH_GSUB_VERT)) {
+ bk_Block *b =
+ bk_new_Block(b16, 1, // Format
+ p16, bk_newBlockFromBuffer(coverageBuf), // coverage
+ b16,
+ subtable->items[0].to.index - subtable->items[0].from.index, // delta
+ bkover);
+ Coverage.free(cov);
+ return bk_build_Block(b);
+ } else {
+ bk_Block *b = bk_new_Block(b16, 2, // Format
+ p16, bk_newBlockFromBuffer(coverageBuf), // coverage
+ b16, subtable->length, // quantity
+ bkover);
+ for (glyphid_t k = 0; k < subtable->length; k++) {
+ bk_push(b, b16, subtable->items[k].to.index, bkover);
+ }
+ Coverage.free(cov);
+ return bk_build_Block(b);
+ }
+}
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-single.h b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-single.h
new file mode 100644
index 00000000000..0991639b993
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl/subtables/gsub-single.h
@@ -0,0 +1,13 @@
+#ifndef CARYLL_TABLE_OTL_GSUB_SINGLE_H
+#define CARYLL_TABLE_OTL_GSUB_SINGLE_H
+
+#include "common.h"
+
+otl_Subtable *otl_read_gsub_single(const font_file_pointer data, uint32_t tableLength,
+ uint32_t subtableOffset, const glyphid_t maxGlyphs,
+ const otfcc_Options *options);
+json_value *otl_gsub_dump_single(const otl_Subtable *_subtable);
+otl_Subtable *otl_gsub_parse_single(const json_value *_subtable, const otfcc_Options *options);
+caryll_Buffer *otfcc_build_gsub_single_subtable(const otl_Subtable *_subtable, otl_BuildHeuristics heuristics);
+
+#endif