diff options
Diffstat (limited to 'Build/source/texk/web2c/mfluadir/otfcc/lib/table/otl')
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 |