summaryrefslogtreecommitdiff
path: root/Build/source/texk/web2c/mfluadir/otfcc/lib/table/cmap.c
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/web2c/mfluadir/otfcc/lib/table/cmap.c')
-rw-r--r--Build/source/texk/web2c/mfluadir/otfcc/lib/table/cmap.c801
1 files changed, 801 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/mfluadir/otfcc/lib/table/cmap.c b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/cmap.c
new file mode 100644
index 00000000000..71518d36db2
--- /dev/null
+++ b/Build/source/texk/web2c/mfluadir/otfcc/lib/table/cmap.c
@@ -0,0 +1,801 @@
+#include "cmap.h"
+
+#include "support/util.h"
+#include "bk/bkgraph.h"
+
+// PART I, type definition
+
+static INLINE void initCmap(table_cmap *cmap) {
+ cmap->unicodes = NULL;
+ cmap->uvs = NULL;
+}
+static INLINE void disposeCmap(table_cmap *cmap) {
+ { // Unicode
+ cmap_Entry *s, *tmp;
+ HASH_ITER(hh, cmap->unicodes, s, tmp) {
+ // delete and free all cmap entries
+ Handle.dispose(&s->glyph);
+ HASH_DEL(cmap->unicodes, s);
+ FREE(s);
+ }
+ }
+ { // UVS
+ cmap_UVS_Entry *s, *tmp;
+ HASH_ITER(hh, cmap->uvs, s, tmp) {
+ // delete and free all cmap entries
+ Handle.dispose(&s->glyph);
+ HASH_DEL(cmap->uvs, s);
+ FREE(s);
+ }
+ }
+}
+caryll_standardRefTypeFn(table_cmap, initCmap, disposeCmap);
+
+static bool otfcc_encodeCmapByIndex(table_cmap *cmap, int c, uint16_t gid) {
+ cmap_Entry *s;
+ HASH_FIND_INT(cmap->unicodes, &c, s);
+ if (s == NULL) {
+ NEW(s);
+ s->glyph = Handle.fromIndex(gid);
+ s->unicode = c;
+ HASH_ADD_INT(cmap->unicodes, unicode, s);
+ return true;
+ } else {
+ return false;
+ }
+}
+static bool otfcc_encodeCmapByName(table_cmap *cmap, int c, sds name) {
+ cmap_Entry *s;
+ HASH_FIND_INT(cmap->unicodes, &c, s);
+ if (s == NULL) {
+ NEW(s);
+ s->glyph = Handle.fromName(name);
+ s->unicode = c;
+ HASH_ADD_INT(cmap->unicodes, unicode, s);
+ return true;
+ } else {
+ return false;
+ }
+}
+static bool otfcc_unmapCmap(table_cmap *cmap, int c) {
+ cmap_Entry *s;
+ HASH_FIND_INT(cmap->unicodes, &c, s);
+ if (s) {
+ Handle.dispose(&s->glyph);
+ HASH_DEL(cmap->unicodes, s);
+ FREE(s);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static otfcc_GlyphHandle *otfcc_cmapLookup(const table_cmap *cmap, int c) {
+ cmap_Entry *s;
+ HASH_FIND_INT(cmap->unicodes, &c, s);
+ if (s) {
+ return &(s->glyph);
+ } else {
+ return NULL;
+ }
+}
+
+static bool otfcc_encodeCmapUVSByIndex(table_cmap *cmap, cmap_UVS_key c, uint16_t gid) {
+ cmap_UVS_Entry *s;
+ HASH_FIND(hh, cmap->uvs, &c, sizeof(cmap_UVS_key), s);
+ if (s == NULL) {
+ NEW(s);
+ s->glyph = Handle.fromIndex(gid);
+ s->key = c;
+ HASH_ADD(hh, cmap->uvs, key, sizeof(cmap_UVS_key), s);
+ return true;
+ } else {
+ return false;
+ }
+}
+static bool otfcc_encodeCmapUVSByName(table_cmap *cmap, cmap_UVS_key c, sds name) {
+ cmap_UVS_Entry *s;
+ HASH_FIND(hh, cmap->uvs, &c, sizeof(cmap_UVS_key), s);
+ if (s == NULL) {
+ NEW(s);
+ s->glyph = Handle.fromName(name);
+ s->key = c;
+ HASH_ADD(hh, cmap->uvs, key, sizeof(cmap_UVS_key), s);
+ return true;
+ } else {
+ return false;
+ }
+}
+static bool otfcc_unmapCmapUVS(table_cmap *cmap, cmap_UVS_key c) {
+ cmap_Entry *s;
+ HASH_FIND(hh, cmap->uvs, &c, sizeof(cmap_UVS_key), s);
+ if (s) {
+ Handle.dispose(&s->glyph);
+ HASH_DEL(cmap->uvs, s);
+ FREE(s);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static otfcc_GlyphHandle *otfcc_cmapLookupUVS(const table_cmap *cmap, cmap_UVS_key c) {
+ cmap_Entry *s;
+ HASH_FIND(hh, cmap->uvs, &c, sizeof(cmap_UVS_key), s);
+ if (s) {
+ return &(s->glyph);
+ } else {
+ return NULL;
+ }
+}
+
+caryll_ElementInterfaceOf(table_cmap) table_iCmap = {caryll_standardRefTypeMethods(table_cmap),
+ .encodeByIndex = otfcc_encodeCmapByIndex,
+ .encodeByName = otfcc_encodeCmapByName,
+ .unmap = otfcc_unmapCmap,
+ .lookup = otfcc_cmapLookup,
+ .encodeUVSByIndex = otfcc_encodeCmapUVSByIndex,
+ .encodeUVSByName = otfcc_encodeCmapUVSByName,
+ .unmapUVS = otfcc_unmapCmapUVS,
+ .lookupUVS = otfcc_cmapLookupUVS};
+
+// PART II, reading and writing
+
+static void readFormat12(font_file_pointer start, uint32_t lengthLimit, table_cmap *cmap) {
+ if (lengthLimit < 16) return;
+ uint32_t nGroups = read_32u(start + 12);
+ if (lengthLimit < 16 + 12 * nGroups) return;
+ for (uint32_t j = 0; j < nGroups; j++) {
+ uint32_t startCode = read_32u(start + 16 + 12 * j);
+ uint32_t endCode = read_32u(start + 16 + 12 * j + 4);
+ uint32_t startGID = read_32u(start + 16 + 12 * j + 8);
+ for (uint32_t c = startCode; c <= endCode; c++) {
+ otfcc_encodeCmapByIndex(cmap, c, (c - startCode) + startGID);
+ }
+ }
+}
+
+static void readFormat4(font_file_pointer start, uint32_t lengthLimit, table_cmap *cmap) {
+ if (lengthLimit < 14) return;
+ uint16_t segmentsCount = read_16u(start + 6) / 2;
+ if (lengthLimit < 16 + segmentsCount * 8) return;
+ for (uint16_t j = 0; j < segmentsCount; j++) {
+ uint16_t endCode = read_16u(start + 14 + j * 2);
+ uint16_t startCode = read_16u(start + 14 + segmentsCount * 2 + 2 + j * 2);
+ int16_t idDelta = read_16u(start + 14 + segmentsCount * 4 + 2 + j * 2);
+ uint32_t idRangeOffsetOffset = 14 + segmentsCount * 6 + 2 + j * 2;
+ uint16_t idRangeOffset = read_16u(start + idRangeOffsetOffset);
+ if (idRangeOffset == 0) {
+ for (uint32_t c = startCode; c < 0xFFFF && c <= endCode; c++) {
+ uint16_t gid = (c + idDelta) & 0xFFFF;
+ otfcc_encodeCmapByIndex(cmap, c, gid);
+ }
+ } else {
+ for (uint32_t c = startCode; c < 0xFFFF && c <= endCode; c++) {
+ uint32_t glyphOffset = idRangeOffset + (c - startCode) * 2 + idRangeOffsetOffset;
+ if (glyphOffset + 2 > lengthLimit) continue; // ignore this encoding slot when o-o-r
+ uint16_t gid = (read_16u(start + glyphOffset) + idDelta) & 0xFFFF;
+ otfcc_encodeCmapByIndex(cmap, c, gid);
+ }
+ }
+ }
+}
+static void readUVSDefault(font_file_pointer start, uint32_t lengthLimit, unicode_t selector,
+ table_cmap *cmap) {
+ if (lengthLimit < 4) return;
+ uint32_t numUnicodeValueRanges = read_32u(start);
+ if (lengthLimit < 4 + 4 * numUnicodeValueRanges) return;
+ for (uint32_t j = 0; j < numUnicodeValueRanges; j++) {
+ font_file_pointer vsr = start + 4 + 4 * j;
+ unicode_t startUnicodeValue = read_24u(vsr);
+ uint8_t additionalCount = read_8u(vsr + 3);
+ for (unicode_t u = startUnicodeValue; u <= startUnicodeValue + additionalCount; u++) {
+ otfcc_GlyphHandle *g = table_iCmap.lookup(cmap, (int)u);
+ if (!g) continue;
+ table_iCmap.encodeUVSByIndex(cmap, (cmap_UVS_key){.unicode = u, .selector = selector},
+ g->index);
+ }
+ }
+}
+static void readUVSNonDefault(font_file_pointer start, uint32_t lengthLimit, unicode_t selector,
+ table_cmap *cmap) {
+ if (lengthLimit < 4) return;
+ uint32_t numUVSMappings = read_32u(start);
+ if (lengthLimit < 4 + 5 * numUVSMappings) return;
+ for (uint32_t j = 0; j < numUVSMappings; j++) {
+ font_file_pointer vsr = start + 4 + 5 * j;
+ unicode_t unicodeValue = read_24u(vsr);
+ glyphid_t glyphID = read_16u(vsr + 3);
+ table_iCmap.encodeUVSByIndex(
+ cmap, (cmap_UVS_key){.unicode = unicodeValue, .selector = selector}, glyphID);
+ }
+}
+static void readFormat14(font_file_pointer start, uint32_t lengthLimit, table_cmap *cmap) {
+ if (lengthLimit < 10) return;
+ uint32_t nGroups = read_32u(start + 6);
+ if (lengthLimit < 11 + 11 * nGroups) return;
+ for (uint32_t j = 0; j < nGroups; j++) {
+ font_file_pointer vsr = start + 10 + 11 * j;
+ unicode_t selector = read_24u(vsr);
+ uint32_t defaultUVSOffset = read_32u(vsr + 3);
+ uint32_t nonDefaultUVSOffset = read_32u(vsr + 7);
+
+ if (defaultUVSOffset) {
+ readUVSDefault(start + defaultUVSOffset, lengthLimit - defaultUVSOffset, selector,
+ cmap);
+ }
+ if (nonDefaultUVSOffset) {
+ readUVSNonDefault(start + nonDefaultUVSOffset, lengthLimit - nonDefaultUVSOffset,
+ selector, cmap);
+ }
+ }
+}
+
+static void readCmapMappingTable(font_file_pointer start, uint32_t lengthLimit, table_cmap *cmap,
+ tableid_t requiredFormat) {
+ uint16_t format = read_16u(start);
+ if (format == requiredFormat) {
+ if (format == 4) {
+ readFormat4(start, lengthLimit, cmap);
+ } else if (format == 12) {
+ readFormat12(start, lengthLimit, cmap);
+ }
+ }
+}
+
+static void readCmapMappingTableUVS(font_file_pointer start, uint32_t lengthLimit,
+ table_cmap *cmap) {
+ uint16_t format = read_16u(start);
+ if (format == 14) { readFormat14(start, lengthLimit, cmap); }
+}
+
+static int by_unicode(cmap_Entry *a, cmap_Entry *b) {
+ return (a->unicode - b->unicode);
+}
+
+static int by_uvs_key(cmap_UVS_Entry *a, cmap_UVS_Entry *b) {
+ if (a->key.unicode == b->key.unicode) {
+ return a->key.selector - b->key.selector;
+ } else {
+ return a->key.unicode - b->key.unicode;
+ }
+}
+
+static INLINE bool isValidCmapEncoding(uint16_t platform, uint16_t encoding) {
+ return (platform == 0 && encoding == 3) || (platform == 0 && encoding == 4) ||
+ (platform == 0 && encoding == 5) || (platform == 3 && encoding == 1) ||
+ (platform == 3 && encoding == 10);
+}
+
+// Note: we do not support Apple's format 0 subtable
+// since we not never support legacy fonts.
+const tableid_t formatPriorities[] = {12, 4, 0};
+
+// OTFCC will not support all `cmap` mappings.
+table_cmap *otfcc_readCmap(const otfcc_Packet packet, const otfcc_Options *options) {
+ // the map is a reference to a hash table
+ table_cmap *cmap = NULL;
+ FOR_TABLE(OTFCC_CHR('c','m','a','p'), table) {
+ font_file_pointer data = table.data;
+ uint32_t length = table.length;
+ if (length < 4) goto CMAP_CORRUPTED;
+
+ cmap = table_iCmap.create(); // intialize to empty hashtable
+ uint16_t numTables = read_16u(data + 2);
+ if (length < 4 + 8 * numTables) goto CMAP_CORRUPTED;
+
+ // step 1 : read format 12. The results are prioritized
+ for (size_t kSubtableType = 0; formatPriorities[kSubtableType]; kSubtableType++) {
+ for (uint16_t j = 0; j < numTables; j++) {
+ uint16_t platform = read_16u(data + 4 + 8 * j);
+ uint16_t encoding = read_16u(data + 4 + 8 * j + 2);
+ if (!isValidCmapEncoding(platform, encoding)) continue;
+
+ uint32_t tableOffset = read_32u(data + 4 + 8 * j + 4);
+ readCmapMappingTable(data + tableOffset, length - tableOffset, cmap,
+ formatPriorities[kSubtableType]);
+ };
+ }
+ HASH_SORT(cmap->unicodes, by_unicode);
+
+ // step 3 : read format 14
+ for (uint16_t j = 0; j < numTables; j++) {
+ uint16_t platform = read_16u(data + 4 + 8 * j);
+ uint16_t encoding = read_16u(data + 4 + 8 * j + 2);
+ if (!isValidCmapEncoding(platform, encoding)) continue;
+
+ uint32_t tableOffset = read_32u(data + 4 + 8 * j + 4);
+ readCmapMappingTableUVS(data + tableOffset, length - tableOffset, cmap);
+ };
+ HASH_SORT(cmap->uvs, by_uvs_key);
+ return cmap;
+
+ CMAP_CORRUPTED:
+ logWarning("table 'cmap' corrupted.\n");
+ if (cmap != NULL) { FREE(cmap), cmap = NULL; }
+ }
+ return NULL;
+}
+
+void otfcc_dumpCmap(const table_cmap *table, json_value *root, const otfcc_Options *options) {
+ if (!table) return;
+ loggedStep("cmap") {
+ if (table->unicodes) {
+ json_value *cmap = json_object_new(HASH_COUNT(table->unicodes));
+ cmap_Entry *item;
+ foreach_hash(item, table->unicodes) if (item->glyph.name) {
+ sds key;
+ if (options->decimal_cmap) {
+ key = sdsfromlonglong(item->unicode);
+ } else {
+ key = sdscatprintf(sdsempty(), "U+%04X", item->unicode);
+ }
+ json_object_push(
+ cmap, key,
+ json_string_new_length((uint32_t)sdslen(item->glyph.name), item->glyph.name));
+ sdsfree(key);
+ }
+ json_object_push(root, "cmap", cmap);
+ }
+ if (table->uvs) {
+ json_value *uvs = json_object_new(HASH_COUNT(table->uvs));
+ cmap_UVS_Entry *item;
+ foreach_hash(item, table->uvs) if (item->glyph.name) {
+ sds key;
+ if (options->decimal_cmap) {
+ key = sdscatprintf(sdsempty(), "%d %d", item->key.unicode, item->key.selector);
+ } else {
+ key = sdscatprintf(sdsempty(), "U+%04X U+%04X", item->key.unicode,
+ item->key.selector);
+ }
+ json_object_push(
+ uvs, key,
+ json_string_new_length((uint32_t)sdslen(item->glyph.name), item->glyph.name));
+ sdsfree(key);
+ }
+ json_object_push(root, "cmap_uvs", uvs);
+ }
+ }
+}
+
+static INLINE unicode_t parseUnicode(const sds unicodeStr) {
+ if (sdslen(unicodeStr) > 2 && unicodeStr[0] == 'U' && unicodeStr[1] == '+') {
+ return strtol(unicodeStr + 2, NULL, 16);
+ } else {
+ return atoi(unicodeStr);
+ }
+}
+
+static void parseCmapUnicodes(table_cmap *cmap, const json_value *table,
+ const otfcc_Options *options) {
+ if (!table || table->type != json_object) return;
+ for (uint32_t j = 0; j < table->u.object.length; j++) {
+ sds unicodeStr =
+ sdsnewlen(table->u.object.values[j].name, table->u.object.values[j].name_length);
+ json_value *item = table->u.object.values[j].value;
+ unicode_t unicode = parseUnicode(unicodeStr);
+ sdsfree(unicodeStr);
+ if (item->type == json_string && unicode > 0 && unicode <= 0x10FFFF) {
+ sds gname = sdsnewlen(item->u.string.ptr, item->u.string.length);
+ if (!otfcc_encodeCmapByName(cmap, unicode, gname)) {
+ glyph_handle *currentMap = otfcc_cmapLookup(cmap, unicode);
+ logWarning("U+%04X is already mapped to %s. Assignment to %s is ignored.", unicode,
+ currentMap->name, gname);
+ }
+ }
+ }
+}
+
+static INLINE cmap_UVS_key parseUVSKey(const sds uvsStr) {
+ size_t len = sdslen(uvsStr);
+ cmap_UVS_key k = {.unicode = 0, .selector = 0};
+ for (sds scan = uvsStr; scan < uvsStr + len; scan++) {
+ if (*scan == ' ') {
+ k.unicode = parseUnicode(uvsStr);
+ k.selector = parseUnicode(scan + 1);
+ return k;
+ }
+ }
+ return k;
+}
+
+static void parseCmapUVS(table_cmap *cmap, const json_value *table, const otfcc_Options *options) {
+ if (!table || table->type != json_object) return;
+ for (uint32_t j = 0; j < table->u.object.length; j++) {
+ sds uvsStr =
+ sdsnewlen(table->u.object.values[j].name, table->u.object.values[j].name_length);
+ cmap_UVS_key k = parseUVSKey(uvsStr);
+ json_value *item = table->u.object.values[j].value;
+ if (item->type == json_string && k.unicode > 0 && k.unicode <= 0x10FFFF && k.selector > 0 &&
+ k.selector <= 0x10FFFF) {
+ sds gname = sdsnewlen(item->u.string.ptr, item->u.string.length);
+ if (!otfcc_encodeCmapUVSByName(cmap, k, gname)) {
+ glyph_handle *currentMap = otfcc_cmapLookupUVS(cmap, k);
+ logWarning(
+ "UVS U+%04X U+%04X is already mapped to %s. Assignment to %s is ignored.",
+ k.unicode, k.selector, currentMap->name, gname);
+ }
+ }
+ }
+}
+
+table_cmap *otfcc_parseCmap(const json_value *root, const otfcc_Options *options) {
+ if (root->type != json_object) return NULL;
+ table_cmap *cmap = table_iCmap.create();
+ loggedStep("cmap") {
+ parseCmapUnicodes(cmap, json_obj_get_type(root, "cmap", json_object), options);
+ }
+ loggedStep("cmap_uvs") {
+ parseCmapUVS(cmap, json_obj_get_type(root, "cmap_uvs", json_object), options);
+ }
+
+ HASH_SORT(cmap->unicodes, by_unicode);
+ HASH_SORT(cmap->uvs, by_uvs_key);
+
+ return cmap;
+}
+// writing tables
+#define FLUSH_SEQUENCE_FORMAT_4 \
+ bufwrite16b(endCount, lastUnicodeEnd); \
+ bufwrite16b(startCount, lastUnicodeStart); \
+ if (isSequencial) { \
+ bufwrite16b(idDelta, lastGIDStart - lastUnicodeStart); \
+ bufwrite16b(idRangeOffset, 0); \
+ } else { \
+ bufwrite16b(idDelta, 0); \
+ bufwrite16b(idRangeOffset, lastGlyphIdArrayOffset + 1); \
+ } \
+ segmentsCount += 1;
+
+static caryll_Buffer *otfcc_buildCmap_format4(const table_cmap *cmap) {
+ caryll_Buffer *buf = bufnew();
+ caryll_Buffer *endCount = bufnew();
+ caryll_Buffer *startCount = bufnew();
+ caryll_Buffer *idDelta = bufnew();
+ caryll_Buffer *idRangeOffset = bufnew();
+ caryll_Buffer *glyphIdArray = bufnew();
+
+ bool started = false;
+ int lastUnicodeStart = 0xFFFFFF;
+ int lastUnicodeEnd = 0xFFFFFF;
+ int lastGIDStart = 0xFFFFFF;
+ int lastGIDEnd = 0xFFFFFF;
+ size_t lastGlyphIdArrayOffset = 0;
+ bool isSequencial = true;
+ uint16_t segmentsCount = 0;
+
+ cmap_Entry *item;
+ foreach_hash(item, cmap->unicodes) if (item->unicode <= 0xFFFF) {
+ if (!started) {
+ started = true;
+ lastUnicodeStart = lastUnicodeEnd = item->unicode;
+ lastGIDStart = lastGIDEnd = item->glyph.index;
+ isSequencial = true;
+ } else {
+ if (item->unicode == lastUnicodeEnd + 1 &&
+ !(item->glyph.index != lastGIDEnd + 1 && isSequencial &&
+ lastGIDEnd - lastGIDStart >= 4)) {
+ if (isSequencial && !(item->glyph.index == lastGIDEnd + 1)) {
+ lastGlyphIdArrayOffset = glyphIdArray->cursor;
+ // oops, sequencial glyphid broken
+ for (int j = lastGIDStart; j <= lastGIDEnd; j++) {
+ bufwrite16b(glyphIdArray, j);
+ }
+ }
+ lastUnicodeEnd = item->unicode;
+ isSequencial = isSequencial && (item->glyph.index == lastGIDEnd + 1);
+ lastGIDEnd = item->glyph.index;
+ if (!isSequencial) { bufwrite16b(glyphIdArray, lastGIDEnd); }
+ } else {
+ // we have a segment
+ FLUSH_SEQUENCE_FORMAT_4;
+
+ lastUnicodeStart = lastUnicodeEnd = item->unicode;
+ lastGIDStart = lastGIDEnd = item->glyph.index;
+ isSequencial = true;
+ }
+ }
+ }
+
+ FLUSH_SEQUENCE_FORMAT_4;
+ if (lastGIDEnd < 0xFFFF) {
+ // Add a padding segment to end this subtable
+ bufwrite16b(endCount, 0xFFFF);
+ bufwrite16b(startCount, 0xFFFF);
+ bufwrite16b(idDelta, 1);
+ bufwrite16b(idRangeOffset, 0);
+ segmentsCount += 1;
+ }
+
+ for (int j = 0; j < segmentsCount; j++) {
+ // rewrite idRangeOffset
+ uint16_t ro = read_16u((uint8_t *)idRangeOffset->data + j * 2);
+ if (ro) {
+ ro -= 1;
+ ro += 2 * (segmentsCount - j);
+ bufseek(idRangeOffset, 2 * j);
+ bufwrite16b(idRangeOffset, ro);
+ }
+ }
+
+ bufwrite16b(buf, 4);
+ bufwrite16b(buf, 0); // fill later
+ bufwrite16b(buf, 0);
+ bufwrite16b(buf, segmentsCount << 1);
+ uint32_t i;
+ uint32_t j;
+ for (j = 0, i = 1; i <= segmentsCount; ++j) {
+ i <<= 1;
+ }
+ bufwrite16b(buf, i);
+ bufwrite16b(buf, j - 1);
+ bufwrite16b(buf, 2 * segmentsCount - i);
+ bufwrite_buf(buf, endCount);
+ bufwrite16b(buf, 0);
+ bufwrite_buf(buf, startCount);
+ bufwrite_buf(buf, idDelta);
+ bufwrite_buf(buf, idRangeOffset);
+ bufwrite_buf(buf, glyphIdArray);
+
+ bufseek(buf, 2);
+ bufwrite16b(buf, buflen(buf));
+
+ buffree(endCount);
+ buffree(startCount);
+ buffree(idDelta);
+ buffree(idRangeOffset);
+ buffree(glyphIdArray);
+ return buf;
+}
+
+static caryll_Buffer *otfcc_tryBuildCmap_format4(const table_cmap *cmap) {
+ caryll_Buffer *buf = otfcc_buildCmap_format4(cmap);
+ if (buflen(buf) > UINT16_MAX) {
+ // this cmap subtable is broken
+ buffree(buf);
+ return NULL;
+ } else
+ return buf;
+}
+
+static caryll_Buffer *otfcc_buildCmap_format12(const table_cmap *cmap) {
+ caryll_Buffer *buf = bufnew();
+ bufwrite16b(buf, 12);
+ bufwrite16b(buf, 0);
+ bufwrite32b(buf, 0); // fill later
+ bufwrite32b(buf, 0);
+ bufwrite32b(buf, 0); // fill later
+
+ uint32_t nGroups = 0;
+ bool started = false;
+ int lastUnicodeStart = 0xFFFFFF;
+ int lastUnicodeEnd = 0xFFFFFF;
+ int lastGIDStart = 0xFFFFFF;
+ int lastGIDEnd = 0xFFFFFF;
+ cmap_Entry *item;
+ foreach_hash(item, cmap->unicodes) {
+ if (!started) {
+ started = true;
+ lastUnicodeStart = lastUnicodeEnd = item->unicode;
+ lastGIDStart = lastGIDEnd = item->glyph.index;
+ } else if (item->unicode == lastUnicodeEnd + 1 && item->glyph.index == lastGIDEnd + 1) {
+ lastUnicodeEnd = item->unicode;
+ lastGIDEnd = item->glyph.index;
+ } else {
+ bufwrite32b(buf, lastUnicodeStart);
+ bufwrite32b(buf, lastUnicodeEnd);
+ bufwrite32b(buf, lastGIDStart);
+ nGroups += 1;
+ lastUnicodeStart = lastUnicodeEnd = item->unicode;
+ lastGIDStart = lastGIDEnd = item->glyph.index;
+ }
+ }
+ bufwrite32b(buf, lastUnicodeStart);
+ bufwrite32b(buf, lastUnicodeEnd);
+ bufwrite32b(buf, lastGIDStart);
+ nGroups += 1;
+
+ bufseek(buf, 4);
+ bufwrite32b(buf, (uint32_t)buflen(buf));
+ bufseek(buf, 12);
+ bufwrite32b(buf, nGroups);
+ return buf;
+}
+
+#define MAX_UNICODE 0x110001
+#define HAS_DEFAULT 1
+#define HAS_NON_DEFAULT 2
+
+static INLINE void writeDefaultRange(caryll_Buffer *dflt, uint32_t *nRanges, unicode_t start,
+ unicode_t end) {
+ // start and end are inclusive
+ while (end - start > 0xFF) {
+ bufwrite24b(dflt, start);
+ bufwrite8(dflt, 0xFF);
+ start += 0x100;
+ *nRanges += 1;
+ }
+ bufwrite24b(dflt, start);
+ bufwrite8(dflt, end - start);
+ *nRanges += 1;
+}
+
+static uint8_t buildFormat14ForSelector(const table_cmap *cmap, unicode_t selector,
+ caryll_Buffer *dflt, caryll_Buffer *nondflt) {
+ glyphid_t *defaults, *nonDefaults;
+ NEW_CLEAN_N(defaults, MAX_UNICODE);
+ NEW_CLEAN_N(nonDefaults, MAX_UNICODE);
+ for (unicode_t s = 0; s < MAX_UNICODE; s++) {
+ defaults[s] = 0xFFFF;
+ nonDefaults[s] = 0xFFFF;
+ }
+ cmap_UVS_Entry *item;
+ foreach_hash(item, cmap->uvs) {
+ unicode_t u = item->key.unicode;
+ if (item->key.selector != selector || u >= MAX_UNICODE) continue;
+ if (!item->glyph.name) continue;
+ glyphid_t uvsGID = item->glyph.index;
+ otfcc_GlyphHandle *g = table_iCmap.lookup(cmap, (int)u);
+ if (!g) {
+ nonDefaults[u] = uvsGID;
+ } else if (uvsGID == g->index) {
+ defaults[u] = uvsGID;
+ } else {
+ nonDefaults[u] = uvsGID;
+ }
+ }
+
+ defaults[0] = nonDefaults[0] = 0xFFFF; // clear U+0000
+ defaults[MAX_UNICODE - 1] = nonDefaults[MAX_UNICODE - 1] = 0xFFFF; // clear U+110000 *
+
+ uint32_t numUnicodeValueRanges = 0;
+ unicode_t startUnicodeValue = 0;
+ uint32_t numUVSMappings = 0;
+ bufwrite32b(dflt, 0);
+ bufwrite32b(nondflt, 0);
+ for (unicode_t u = 1; u < MAX_UNICODE; u++) {
+ if (defaults[u] != 0xFFFF && defaults[u - 1] == 0xFFFF) { startUnicodeValue = u; }
+ if (defaults[u] == 0xFFFF && defaults[u - 1] != 0xFFFF) {
+ writeDefaultRange(dflt, &numUnicodeValueRanges, startUnicodeValue, u - 1);
+ }
+ // non-defaults
+ if (nonDefaults[u] != 0xFFFF) {
+ bufwrite24b(nondflt, u);
+ bufwrite16b(nondflt, nonDefaults[u]);
+ numUVSMappings++;
+ }
+ }
+ bufseek(dflt, 0);
+ bufwrite32b(dflt, numUnicodeValueRanges);
+ bufseek(nondflt, 0);
+ bufwrite32b(nondflt, numUVSMappings);
+
+ FREE(defaults);
+ FREE(nonDefaults);
+
+ return (numUnicodeValueRanges ? HAS_DEFAULT : 0) | (numUVSMappings ? HAS_NON_DEFAULT : 0);
+}
+
+static caryll_Buffer *otfcc_buildCmap_format14(const table_cmap *cmap) {
+ bool *validSelectors;
+ NEW_CLEAN_N(validSelectors, MAX_UNICODE);
+ cmap_UVS_Entry *item;
+ foreach_hash(item, cmap->uvs) {
+ if (item->key.selector < MAX_UNICODE) { validSelectors[item->key.selector] = true; }
+ }
+ uint32_t nSelectors = 0;
+ for (unicode_t selector = 0; selector < MAX_UNICODE; selector++) {
+ if (!validSelectors[selector]) continue;
+ nSelectors++;
+ }
+
+ bk_Block *st = bk_new_Block(b16, 14, // format
+ b32, 0, // length -- fill later
+ b32, nSelectors, // selector quantity
+ bkover);
+
+ for (unicode_t selector = 0; selector < MAX_UNICODE; selector++) {
+ if (!validSelectors[selector]) continue;
+ caryll_Buffer *dflt = bufnew();
+ caryll_Buffer *nondflt = bufnew();
+ uint8_t results = buildFormat14ForSelector(cmap, selector, dflt, nondflt);
+ if (!(results & HAS_DEFAULT)) {
+ buffree(dflt);
+ dflt = NULL;
+ }
+ if (!(results & HAS_NON_DEFAULT)) {
+ buffree(nondflt);
+ nondflt = NULL;
+ }
+ bk_push(st, b8, (selector >> 16) & 0xFF, // selector, first byte
+ b8, (selector >> 8) & 0xFF, // selector, first byte
+ b8, (selector >> 0) & 0xFF, // selector, first byte
+ p32, bk_newBlockFromBuffer(dflt), // default offset
+ p32, bk_newBlockFromBuffer(nondflt), // non-default offset
+ bkover);
+ }
+
+ caryll_Buffer *buf = bk_build_Block(st);
+ bufseek(buf, 2);
+ bufwrite32b(buf, (uint32_t)buflen(buf));
+ return buf;
+}
+
+caryll_Buffer *otfcc_buildCmap(const table_cmap *cmap, const otfcc_Options *options) {
+ if (!cmap || !cmap->unicodes) return NULL;
+
+ cmap_Entry *entry;
+ bool requiresFormat12 = false;
+ bool hasUVS = cmap->uvs && (HASH_COUNT(cmap->uvs) > 0);
+ foreach_hash(entry, cmap->unicodes) {
+ if (entry->unicode > 0xFFFF) { requiresFormat12 = true; }
+ }
+
+ caryll_Buffer *format4 = NULL;
+ if (!requiresFormat12 || !options->stub_cmap4) {
+ format4 = otfcc_tryBuildCmap_format4(cmap);
+ if (!format4)
+ requiresFormat12 = true;
+ }
+
+ uint8_t nTables = requiresFormat12 ? 4 : 2;
+ if (hasUVS) nTables += 1;
+
+ if (!format4) {
+ // Write a dummy
+ format4 = bufnew();
+ bufwrite16b(format4, 4); // format
+ bufwrite16b(format4, 32); // length
+ bufwrite16b(format4, 0); // language
+ bufwrite16b(format4, 4); // segCountX2
+ bufwrite16b(format4, 4); // searchRange
+ bufwrite16b(format4, 1); // entrySelector
+ bufwrite16b(format4, 0); // rangeShift
+ bufwrite16b(format4, 0); // endCount
+ bufwrite16b(format4, 0xFFFF); // endCount
+ bufwrite16b(format4, 0); // endCount
+ bufwrite16b(format4, 0); // startCount
+ bufwrite16b(format4, 0xFFFF); // startCount
+ bufwrite16b(format4, 0); // idDelta
+ bufwrite16b(format4, 1); // idDelta
+ bufwrite16b(format4, 0); // idRangeOffset
+ bufwrite16b(format4, 0); // idRangeOffset
+ }
+
+ caryll_Buffer *format12 = otfcc_buildCmap_format12(cmap);
+
+ bk_Block *root = bk_new_Block(b16, 0, // version
+ b16, nTables, // nTables
+ bkover);
+ bk_push(root, b16, 0, // unicode
+ b16, 3, // BMP
+ p32, bk_newBlockFromBufferCopy(format4), // table
+ bkover);
+ if (requiresFormat12) {
+ bk_push(root, b16, 0, // unicode
+ b16, 4, // full
+ p32, bk_newBlockFromBufferCopy(format12), // table
+ bkover);
+ }
+ if (hasUVS) {
+ caryll_Buffer *format14 = otfcc_buildCmap_format14(cmap);
+ bk_push(root, b16, 0, // Unicode
+ b16, 5, // Variation
+ p32, bk_newBlockFromBuffer(format14), // table
+ bkover);
+ }
+ bk_push(root, b16, 3, // Windows
+ b16, 1, // Unicode BMP
+ p32, bk_newBlockFromBufferCopy(format4), // table
+ bkover);
+ if (requiresFormat12) {
+ bk_push(root, b16, 3, // Windows
+ b16, 10, // Unicode Full
+ p32, bk_newBlockFromBufferCopy(format12), // table
+ bkover);
+ }
+
+ buffree(format4);
+ buffree(format12);
+ return bk_build_Block(root);
+}