diff options
Diffstat (limited to 'Build/source/texk/web2c/mfluadir/otfcc/lib/consolidate/consolidate.c')
-rw-r--r-- | Build/source/texk/web2c/mfluadir/otfcc/lib/consolidate/consolidate.c | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/consolidate/consolidate.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/consolidate/consolidate.c new file mode 100644 index 00000000000..82c22a53302 --- /dev/null +++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/consolidate/consolidate.c @@ -0,0 +1,532 @@ +#include "consolidate.h" + +#include "support/util.h" +#include "table/all.h" + +#include "otl/gsub-single.h" +#include "otl/gsub-multi.h" +#include "otl/gsub-ligature.h" +#include "otl/gsub-reverse.h" +#include "otl/gpos-single.h" +#include "otl/gpos-pair.h" +#include "otl/gpos-cursive.h" +#include "otl/chaining.h" +#include "otl/mark.h" +#include "otl/GDEF.h" + +// Consolidation +// Replace name entries in json to ids and do some check +static int by_stem_pos(const glyf_PostscriptStemDef *a, const glyf_PostscriptStemDef *b) { + if (a->position == b->position) { + return (int)a->map - (int)b->map; + } else if (a->position > b->position) { + return 1; + } else { + return -1; + } +} +static int by_mask_pointindex(const glyf_PostscriptHintMask *a, const glyf_PostscriptHintMask *b) { + return a->contoursBefore == b->contoursBefore ? a->pointsBefore - b->pointsBefore + : a->contoursBefore - b->contoursBefore; +} + +static void consolidateGlyphContours(glyf_Glyph *g, const otfcc_Options *options) { + shapeid_t nContoursConsolidated = 0; + shapeid_t skip = 0; + for (shapeid_t j = 0; j < g->contours.length; j++) { + if (g->contours.items[j].length) { + g->contours.items[j - skip] = g->contours.items[j]; + nContoursConsolidated += 1; + } else { + glyf_iContourList.disposeItem(&g->contours, j); + logWarning("[Consolidate] Removed empty contour #%d in glyph %s.\n", j, g->name); + skip += 1; + } + } + g->contours.length = nContoursConsolidated; +} +static void consolidateGlyphReferences(glyf_Glyph *g, otfcc_Font *font, + const otfcc_Options *options) { + shapeid_t nReferencesConsolidated = 0; + shapeid_t skip = 0; + for (shapeid_t j = 0; j < g->references.length; j++) { + if (!GlyphOrder.consolidateHandle(font->glyph_order, &g->references.items[j].glyph)) { + logWarning("[Consolidate] Ignored absent glyph component reference /%s within /%s.\n", + g->references.items[j].glyph.name, g->name); + glyf_iReferenceList.disposeItem(&(g->references), j); + skip += 1; + } else { + g->references.items[j - skip] = g->references.items[j]; + nReferencesConsolidated += 1; + } + } + g->references.length = nReferencesConsolidated; +} +static void consolidateGlyphHints(glyf_Glyph *g, const otfcc_Options *options) { + // sort stems + if (g->stemH.length) { + for (shapeid_t j = 0; j < g->stemH.length; j++) { + g->stemH.items[j].map = j; + } + glyf_iStemDefList.sort(&g->stemH, by_stem_pos); + } + if (g->stemV.length) { + for (shapeid_t j = 0; j < g->stemV.length; j++) { + g->stemV.items[j].map = j; + } + glyf_iStemDefList.sort(&g->stemV, by_stem_pos); + } + shapeid_t *hmap; + NEW(hmap, g->stemH.length); + shapeid_t *vmap; + NEW(vmap, g->stemV.length); + for (shapeid_t j = 0; j < g->stemH.length; j++) { + hmap[g->stemH.items[j].map] = j; + } + for (shapeid_t j = 0; j < g->stemV.length; j++) { + vmap[g->stemV.items[j].map] = j; + } + // sort masks + if (g->hintMasks.length) { + glyf_iMaskList.sort(&g->hintMasks, by_mask_pointindex); + for (shapeid_t j = 0; j < g->hintMasks.length; j++) { + glyf_PostscriptHintMask oldmask = g->hintMasks.items[j]; // copy + for (shapeid_t k = 0; k < g->stemH.length; k++) { + g->hintMasks.items[j].maskH[k] = oldmask.maskH[hmap[k]]; + } + for (shapeid_t k = 0; k < g->stemV.length; k++) { + g->hintMasks.items[j].maskV[k] = oldmask.maskV[vmap[k]]; + } + } + } + if (g->contourMasks.length) { + glyf_iMaskList.sort(&g->contourMasks, by_mask_pointindex); + for (shapeid_t j = 0; j < g->contourMasks.length; j++) { + glyf_PostscriptHintMask oldmask = g->contourMasks.items[j]; // copy + for (shapeid_t k = 0; k < g->stemH.length; k++) { + g->contourMasks.items[j].maskH[k] = oldmask.maskH[hmap[k]]; + } + for (shapeid_t k = 0; k < g->stemV.length; k++) { + g->contourMasks.items[j].maskV[k] = oldmask.maskV[vmap[k]]; + } + } + } + FREE(hmap); + FREE(vmap); +} +static void consolidateFDSelect(fd_handle *h, table_CFF *cff, const otfcc_Options *options, + const sds gname) { + if (!cff || !cff->fdArray || !cff->fdArrayCount) return; + // Consolidate fdSelect + if (h->state == HANDLE_STATE_INDEX) { + if (h->index >= cff->fdArrayCount) { h->index = 0; } + Handle.consolidateTo(h, h->index, cff->fdArray[h->index]->fontName); + } else if (h->name) { + bool found = false; + for (tableid_t j = 0; j < cff->fdArrayCount; j++) { + if (strcmp(h->name, cff->fdArray[j]->fontName) == 0) { + found = true; + Handle.consolidateTo(h, j, cff->fdArray[j]->fontName); + break; + } + } + if (!found) { + logWarning("[Consolidate] CID Subfont %s is not defined. (in glyph /%s).\n", h->name, + gname); + Handle.dispose(h); + } + } else if (h->name) { + Handle.dispose(h); + } +} +static void consolidateGlyph(glyf_Glyph *g, otfcc_Font *font, const otfcc_Options *options) { + consolidateGlyphContours(g, options); + consolidateGlyphReferences(g, font, options); + consolidateGlyphHints(g, options); + consolidateFDSelect(&g->fdSelect, font->CFF_, options, g->name); +} + +// Anchored reference consolidation +bool consolidateAnchorRef(table_glyf *table, glyf_ComponentReference *gr, + glyf_ComponentReference *rr, const otfcc_Options *options); +static bool getPointCoordinates(table_glyf *table, glyf_ComponentReference *gr, shapeid_t n, + shapeid_t *stated, VQ *x, VQ *y, const otfcc_Options *options) { + glyphid_t j = gr->glyph.index; + glyf_Glyph *g = table->items[j]; + for (shapeid_t c = 0; c < g->contours.length; c++) { + for (shapeid_t pj = 0; pj < g->contours.items[c].length; pj++) { + if (*stated == n) { + glyf_Point *p = &(g->contours.items[c].items[pj]); + iVQ.replace(x, iVQ.pointLinearTfm(gr->x, gr->a, p->x, gr->b, p->y)); + iVQ.replace(y, iVQ.pointLinearTfm(gr->y, gr->c, p->x, gr->d, p->y)); + return true; + } + *stated += 1; + } + } + for (shapeid_t r = 0; r < g->references.length; r++) { + glyf_ComponentReference *rr = &(g->references.items[r]); + + consolidateAnchorRef(table, gr, rr, options); + + // composite affine transformations + glyf_ComponentReference ref = glyf_iComponentReference.empty(); + ref.glyph = Handle.fromIndex(g->references.items[r].glyph.index); + ref.a = gr->a * rr->a + rr->b * gr->c; + ref.b = rr->a * gr->b + rr->b * gr->d; + ref.c = gr->a * rr->c + gr->c * rr->d; + ref.d = gr->b * rr->c + rr->d * gr->d; + iVQ.replace(&ref.x, iVQ.pointLinearTfm(rr->x, rr->a, gr->x, rr->b, gr->y)); + iVQ.replace(&ref.y, iVQ.pointLinearTfm(rr->y, rr->c, gr->x, rr->d, gr->y)); + + bool success = getPointCoordinates(table, &ref, n, stated, x, y, options); + glyf_iComponentReference.dispose(&ref); + if (success) return true; + } + return false; +} + +bool consolidateAnchorRef(table_glyf *table, glyf_ComponentReference *gr, + glyf_ComponentReference *rr, const otfcc_Options *options) { + if (rr->isAnchored == REF_ANCHOR_CONSOLIDATED || rr->isAnchored == REF_XY) return true; + if (rr->isAnchored == REF_ANCHOR_CONSOLIDATING_ANCHOR || + rr->isAnchored == REF_ANCHOR_CONSOLIDATING_XY) { + logWarning( + "Found circular reference of out-of-range point reference in anchored reference."); + rr->isAnchored = REF_XY; + return false; + } + if (rr->isAnchored == REF_ANCHOR_ANCHOR) { + rr->isAnchored = REF_ANCHOR_CONSOLIDATING_ANCHOR; + } else { + rr->isAnchored = REF_ANCHOR_CONSOLIDATING_XY; + } + VQ innerX = iVQ.neutral(), outerX = iVQ.neutral(); + VQ innerY = iVQ.neutral(), outerY = iVQ.neutral(); + shapeid_t innerCounter = 0, outerCounter = 0; + + glyf_ComponentReference rr1 = glyf_iComponentReference.empty(); + rr1.glyph = Handle.fromIndex(rr->glyph.index); + + bool s1 = getPointCoordinates(table, gr, rr->outer, &outerCounter, &outerX, &outerY, options); + bool s2 = getPointCoordinates(table, &rr1, rr->inner, &innerCounter, &innerX, &innerY, options); + if (!s1) { logWarning("Failed to access point %d in outer glyph.", rr->outer); } + if (!s2) { + logWarning("Failed to access point %d in reference to %s.", rr->outer, rr->glyph.name); + } + + VQ rrx = iVQ.pointLinearTfm(outerX, -rr->a, innerX, -rr->b, innerY); + VQ rry = iVQ.pointLinearTfm(outerY, -rr->c, innerX, -rr->d, innerY); + + if (rr->isAnchored == REF_ANCHOR_CONSOLIDATING_ANCHOR) { + iVQ.replace(&rr->x, rrx); + iVQ.replace(&rr->y, rry); + rr->isAnchored = REF_ANCHOR_CONSOLIDATED; + } else { + if (fabs(iVQ.getStill(rr->x) - iVQ.getStill(rrx)) > 0.5 && + fabs(iVQ.getStill(rr->y) - iVQ.getStill(rry)) > 0.5) { + logWarning("Anchored reference to %s does not match its X/Y offset data.", + rr->glyph.name); + } + rr->isAnchored = REF_ANCHOR_CONSOLIDATED; + iVQ.dispose(&rrx), iVQ.dispose(&rry); + } + glyf_iComponentReference.dispose(&rr1); + iVQ.dispose(&innerX), iVQ.dispose(&innerY); + iVQ.dispose(&outerX), iVQ.dispose(&outerY); + return false; +} + +static void consolidateGlyf(otfcc_Font *font, const otfcc_Options *options) { + if (!font->glyph_order || !font->glyf) return; + for (glyphid_t j = 0; j < font->glyf->length; j++) { + if (font->glyf->items[j]) { + consolidateGlyph(font->glyf->items[j], font, options); + } else { + font->glyf->items[j] = otfcc_newGlyf_glyph(); + } + } + for (glyphid_t j = 0; j < font->glyf->length; j++) { + glyf_Glyph *g = font->glyf->items[j]; + loggedStep("%s", g->name) { + glyf_ComponentReference gr = glyf_iComponentReference.empty(); + gr.glyph = Handle.fromIndex(j); + + for (shapeid_t r = 0; r < g->references.length; r++) { + glyf_ComponentReference *rr = &(g->references.items[r]); + consolidateAnchorRef(font->glyf, &gr, rr, options); + } + glyf_iComponentReference.dispose(&gr); + } + } +} + +static void consolidateCmap(otfcc_Font *font, const otfcc_Options *options) { + if (font->glyph_order && font->cmap) { + cmap_Entry *item; + foreach_hash(item, font->cmap->unicodes) { + if (!GlyphOrder.consolidateHandle(font->glyph_order, &item->glyph)) { + logWarning("[Consolidate] Ignored mapping U+%04X to non-existent glyph /%s.\n", + item->unicode, item->glyph.name); + Handle.dispose(&item->glyph); + } + } + } + if (font->glyph_order && font->cmap) { + cmap_UVS_Entry *item; + foreach_hash(item, font->cmap->uvs) { + if (!GlyphOrder.consolidateHandle(font->glyph_order, &item->glyph)) { + logWarning("[Consolidate] Ignored UVS mapping [U+%04X U+%04X] to non-existent " + "glyph /%s.\n", + item->key.unicode, item->key.selector, item->glyph.name); + Handle.dispose(&item->glyph); + } + } + } +} + +typedef bool (*otl_consolidation_function)(otfcc_Font *, table_OTL *, otl_Subtable *, + const otfcc_Options *); +typedef void (*subtable_remover)(otl_Subtable *); +#define LOOKUP_CONSOLIDATOR(llt, fn, fndel) \ + __declare_otl_consolidation(llt, fn, (subtable_remover)fndel, font, table, lookup, options); + +static void __declare_otl_consolidation(otl_LookupType type, otl_consolidation_function fn, + subtable_remover fndel, otfcc_Font *font, table_OTL *table, + otl_Lookup *lookup, const otfcc_Options *options) { + if (!lookup || !lookup->subtables.length || lookup->type != type) return; + loggedStep("%s", lookup->name) { + for (tableid_t j = 0; j < lookup->subtables.length; j++) { + if (!lookup->subtables.items[j]) { + logWarning("[Consolidate] Ignored empty subtable %d of lookup %s.\n", j, + lookup->name); + continue; + } + bool subtableRemoved; + // loggedStep("Subtable %d", j) { + subtableRemoved = fn(font, table, lookup->subtables.items[j], options); + //} + if (subtableRemoved) { + fndel(lookup->subtables.items[j]); + lookup->subtables.items[j] = NULL; + logWarning("[Consolidate] Ignored empty subtable %d of lookup %s.\n", j, + lookup->name); + } + } + tableid_t k = 0; + for (tableid_t j = 0; j < lookup->subtables.length; j++) { + if (lookup->subtables.items[j]) { + lookup->subtables.items[k++] = lookup->subtables.items[j]; + } + } + lookup->subtables.length = k; + if (!k) { + logWarning("[Consolidate] Lookup %s is empty and will be removed.\n", lookup->name); + } + } +} + +static void otfcc_consolidate_lookup(otfcc_Font *font, table_OTL *table, otl_Lookup *lookup, + const otfcc_Options *options) { + LOOKUP_CONSOLIDATOR(otl_type_gsub_single, consolidate_gsub_single, iSubtable_gsub_single.free); + LOOKUP_CONSOLIDATOR(otl_type_gsub_multiple, consolidate_gsub_multi, iSubtable_gsub_multi.free); + LOOKUP_CONSOLIDATOR(otl_type_gsub_alternate, consolidate_gsub_alternative, + iSubtable_gsub_multi.free); + LOOKUP_CONSOLIDATOR(otl_type_gsub_ligature, consolidate_gsub_ligature, + iSubtable_gsub_ligature.free); + LOOKUP_CONSOLIDATOR(otl_type_gsub_chaining, consolidate_chaining, iSubtable_chaining.free); + LOOKUP_CONSOLIDATOR(otl_type_gsub_reverse, consolidate_gsub_reverse, + iSubtable_gsub_reverse.free); + LOOKUP_CONSOLIDATOR(otl_type_gpos_single, consolidate_gpos_single, iSubtable_gpos_single.free); + LOOKUP_CONSOLIDATOR(otl_type_gpos_pair, consolidate_gpos_pair, iSubtable_gpos_pair.free); + LOOKUP_CONSOLIDATOR(otl_type_gpos_cursive, consolidate_gpos_cursive, + iSubtable_gpos_cursive.free); + LOOKUP_CONSOLIDATOR(otl_type_gpos_chaining, consolidate_chaining, iSubtable_chaining.free); + LOOKUP_CONSOLIDATOR(otl_type_gpos_markToBase, consolidate_mark_to_single, + iSubtable_gpos_markToSingle.free); + LOOKUP_CONSOLIDATOR(otl_type_gpos_markToMark, consolidate_mark_to_single, + iSubtable_gpos_markToSingle.free); + LOOKUP_CONSOLIDATOR(otl_type_gpos_markToLigature, consolidate_mark_to_ligature, + iSubtable_gpos_markToLigature.free); +} + +static bool lookupRefIsNotEmpty(const otl_LookupRef *rLut, void *env) { + return rLut && *rLut && (*rLut)->subtables.length > 0; +} +static bool featureRefIsNotEmpty(const otl_FeatureRef *rFeat, void *env) { + return rFeat && *rFeat && (*rFeat)->lookups.length > 0; +} +static bool lookupIsNotEmpty(const otl_LookupPtr *rLut, void *env) { + return rLut && *rLut && (*rLut)->subtables.length > 0; +} +static bool featureIsNotEmpty(const otl_FeaturePtr *rFeat, void *env) { + return rFeat && *rFeat && (*rFeat)->lookups.length > 0; +} + +static void consolidateOTLTable(otfcc_Font *font, table_OTL *table, const otfcc_Options *options) { + if (!font->glyph_order || !table) return; + do { + tableid_t featN = table->features.length; + tableid_t lutN = table->lookups.length; + + // Perform consolidation + for (tableid_t j = 0; j < table->lookups.length; j++) { + otfcc_consolidate_lookup(font, table, table->lookups.items[j], options); + } + // remove empty features + for (tableid_t j = 0; j < table->features.length; j++) { + otl_Feature *feature = table->features.items[j]; + otl_iLookupRefList.filterEnv(&feature->lookups, lookupRefIsNotEmpty, NULL); + } + // remove empty lookups + for (tableid_t j = 0; j < table->languages.length; j++) { + otl_LanguageSystem *lang = table->languages.items[j]; + otl_iFeatureRefList.filterEnv(&lang->features, featureRefIsNotEmpty, NULL); + } + otl_iLookupList.filterEnv(&table->lookups, lookupIsNotEmpty, NULL); + otl_iFeatureList.filterEnv(&table->features, featureIsNotEmpty, NULL); + + tableid_t featN1 = table->features.length; + tableid_t lutN1 = table->lookups.length; + if (featN1 >= featN && lutN1 >= lutN) break; + } while (true); +} + +static void consolidateOTL(otfcc_Font *font, const otfcc_Options *options) { + loggedStep("GSUB") { + consolidateOTLTable(font, font->GSUB, options); + } + loggedStep("GPOS") { + consolidateOTLTable(font, font->GPOS, options); + } + loggedStep("GDEF") { + consolidate_GDEF(font, font->GDEF, options); + } +} + +static void consolidateCOLR(otfcc_Font *font, const otfcc_Options *options) { + if (!font || !font->COLR || !font->glyph_order) return; + table_COLR *consolidated = table_iCOLR.create(); + foreach (colr_Mapping *mapping, *(font->COLR)) { + if (!GlyphOrder.consolidateHandle(font->glyph_order, &mapping->glyph)) { + logWarning("[Consolidate] Ignored missing glyph of /%s", mapping->glyph.name); + continue; + } + colr_Mapping m; + Handle.copy(&m.glyph, &mapping->glyph); + colr_iLayerList.init(&m.layers); + foreach (colr_Layer *layer, mapping->layers) { + if (!GlyphOrder.consolidateHandle(font->glyph_order, &layer->glyph)) { + logWarning("[Consolidate] Ignored missing glyph of /%s", layer->glyph.name); + continue; + } + colr_Layer layer1; + colr_iLayer.copy(&layer1, layer); + colr_iLayerList.push(&m.layers, layer1); + } + if (mapping->layers.length) { + table_iCOLR.push(consolidated, m); + } else { + logWarning("[Consolidate] COLR decomposition for /%s is empth", mapping->glyph.name); + colr_iMapping.dispose(&m); + } + } + table_iCOLR.free(font->COLR); + font->COLR = consolidated; +} + +static int compareTSIEntry(const tsi_Entry *a, const tsi_Entry *b) { + if (a->type != b->type) return a->type - b->type; + return a->glyph.index - b->glyph.index; +} + +static void consolidateTSI(otfcc_Font *font, table_TSI **_tsi, const otfcc_Options *options) { + table_TSI *tsi = *_tsi; + if (!font || !font->glyf || !tsi || !font->glyph_order) return; + table_TSI *consolidated = table_iTSI.create(); + sds *gidEntries; + NEW_CLEAN_N(gidEntries, font->glyf->length); + + foreach (tsi_Entry *entry, *tsi) { + if (entry->type == TSI_GLYPH) { + if (GlyphOrder.consolidateHandle(font->glyph_order, &entry->glyph)) { + if (gidEntries[entry->glyph.index]) sdsfree(gidEntries[entry->glyph.index]); + gidEntries[entry->glyph.index] = entry->content; + entry->content = NULL; + } else { + logWarning("[Consolidate] Ignored missing glyph of /%s", entry->glyph.name); + } + } else { + tsi_Entry e; + tsi_iEntry.copy(&e, entry); + table_iTSI.push(consolidated, e); + } + } + for (glyphid_t j = 0; j < font->glyf->length; j++) { + tsi_Entry e; + e.type = TSI_GLYPH; + e.glyph = Handle.fromIndex(j); + GlyphOrder.consolidateHandle(font->glyph_order, &e.glyph); + e.content = gidEntries[j] ? gidEntries[j] : sdsempty(); + table_iTSI.push(consolidated, e); + } + table_iTSI.free(tsi); + FREE(gidEntries); + table_iTSI.sort(consolidated, compareTSIEntry); + *_tsi = consolidated; +} + +void otfcc_consolidateFont(otfcc_Font *font, const otfcc_Options *options) { + // In case we don’t have a glyph order, make one. + if (font->glyf && !font->glyph_order) { + otfcc_GlyphOrder *go = GlyphOrder.create(); + for (glyphid_t j = 0; j < font->glyf->length; j++) { + sds name; + sds glyfName = font->glyf->items[j]->name; + if (glyfName) { + name = sdsdup(glyfName); + } else { + name = sdscatprintf(sdsempty(), "$$gid%d", j); + font->glyf->items[j]->name = sdsdup(name); + } + if (!GlyphOrder.setByName(go, name, j)) { + logWarning("[Consolidate] Glyph name %s is already in use.", name); + uint32_t suffix = 2; + bool success = false; + do { + sds newname = sdscatfmt(sdsempty(), "%s_%u", name, suffix); + success = GlyphOrder.setByName(go, newname, j); + if (!success) { + sdsfree(newname); + suffix += 1; + } else { + logWarning("[Consolidate] Glyph %s is renamed into %s.", name, newname); + sdsfree(font->glyf->items[j]->name); + font->glyf->items[j]->name = sdsdup(newname); + } + } while (!success); + sdsfree(name); + } + } + font->glyph_order = go; + } + loggedStep("glyf") { + consolidateGlyf(font, options); + } + loggedStep("cmap") { + consolidateCmap(font, options); + } + if (font->glyf) consolidateOTL(font, options); + loggedStep("COLR") { + consolidateCOLR(font, options); + } + loggedStep("TSI_01") { + consolidateTSI(font, &font->TSI_01, options); + } + loggedStep("TSI_23") { + consolidateTSI(font, &font->TSI_23, options); + } + loggedStep("TSI5") { + fontop_consolidateClassDef(font, font->TSI5, options); + } +} |