packages feed

fontconfig-pure-0.5.0.0: cbits/transcode.c

#include "cmp.h"
#include <fontconfig/fontconfig.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

struct bytes {
    uint8_t *start;
    size_t offset;
    size_t length;
};

bool bytes_reader(struct cmp_ctx_s *ctx, void *data, size_t limit) {
    struct bytes *bytes = ctx->buf;
    if (bytes->offset + limit > bytes->length) return false;
    memcpy(data, bytes->start + bytes->offset, limit);
    bytes->offset += limit;
    return true;
}

bool bytes_skipper(struct cmp_ctx_s *ctx, size_t count) {
    struct bytes *bytes = ctx->buf;
    if (bytes->offset + count > bytes->length) return false;
    bytes->offset += count;
    return true;
}

size_t bytes_writer(struct cmp_ctx_s *ctx, const void *data, size_t count) {
    struct bytes *bytes = ctx->buf;
    if (bytes->offset + count > bytes->length) {
        bytes->start = realloc(bytes->start, 2*bytes->length);
        if (bytes->start == NULL) return 0;
        bytes->length = 2*bytes->length;
    }
    memcpy(bytes->start + bytes->offset, data, count);
    bytes->offset += count;
    return count;
}

bool cmp_bytes_init(cmp_ctx_t *ctx, uint8_t *buf, size_t length) {
    struct bytes *inner = malloc(sizeof(struct bytes));
    if (inner == NULL) return false;
    inner->start = buf;
    inner->offset = 0;
    inner->length = length;
    cmp_init(ctx, inner, bytes_reader, bytes_skipper, bytes_writer);
    return true;
}

bool cmp_bytes_alloc(cmp_ctx_t *ctx, size_t length) {
    uint8_t *bytes = malloc(length);
    if (bytes == NULL) return false;
    return cmp_bytes_init(ctx, bytes, length);
}

void cmp_bytes_free(cmp_ctx_t *ctx) {
    struct bytes *bytes = ctx->buf;
    free(bytes->start);
    free(bytes);
}

uint8_t *cmp_bytes_take(cmp_ctx_t *ctx, size_t *length) {
    struct bytes *bytes = ctx->buf;
    uint8_t *ret = bytes->start;
    if (length != NULL) *length = bytes->offset;
    free(bytes);
    return ret;
}

// The encoders, decoders, & (for testing) round-trip routines themselves!

FcStrSet *decodeStrSet(cmp_ctx_t *bytes) {
    if (bytes == NULL) return NULL;

    uint32_t size;
    if (!cmp_read_array(bytes, &size)) return NULL;

    FcStrSet *ret = FcStrSetCreate();
    if (ret == NULL) return NULL;

    for (uint32_t i = 0; i < size; i++) {
        char text[512]; // What's the appropriate size here?
        uint32_t str_size = 512;
        if (!cmp_read_str(bytes, text, &str_size)) goto fail;
        if (!FcStrSetAdd(ret, text)) goto fail;
    }
    return ret;
fail:
    FcStrSetDestroy(ret);
    return NULL;
}

FcStrSet *decodeFileSet(cmp_ctx_t *bytes) {
    if (bytes == NULL) return NULL;

    uint32_t size;
    if (!cmp_read_array(bytes, &size)) return NULL;

    FcStrSet *ret = FcStrSetCreate();
    if (ret == NULL) return NULL;

    for (uint32_t i = 0; i < size; i++) {
        char text[512]; // What's the appropriate size here?
        uint32_t str_size = 512;
        if (!cmp_read_str(bytes, text, &str_size)) goto fail;
        if (!FcStrSetAddFilename(ret, text)) goto fail;
    }
    return ret;
fail:
    FcStrSetDestroy(ret);
    return NULL;
}

bool encodeStrList(cmp_ctx_t *bytes, FcStrList *iter) {
    uint32_t size = 0;
    FcStrListFirst(iter);
    while (FcStrListNext(iter) != NULL) size++;
    if (!cmp_write_array(bytes, size)) return false;

    FcStrListFirst(iter);
    char *text = FcStrListNext(iter);
    while (text != NULL) {
        if (!cmp_write_str(bytes, text, strlen(text))) return false;
        text = FcStrListNext(iter);
    }
    return true;
}

bool encodeStrSet(cmp_ctx_t *bytes, FcStrSet *data) {
    if (bytes == NULL || data == NULL) return false;

    FcStrList *iter = FcStrListCreate(data);
    if (iter == NULL) return false;

    bool ret = encodeStrList(bytes, iter);
    FcStrListDone(iter);
    return ret;
}

uint8_t *testStrSet(uint8_t *in, size_t in_length, size_t *length) {
    if (in == NULL) return NULL;

    cmp_ctx_t bytes;
    if (!cmp_bytes_init(&bytes, in, in_length)) return false;
    FcStrSet *decoded = decodeStrSet(&bytes);
    cmp_bytes_take(&bytes, NULL);
    if (decoded == NULL) return NULL;

    if (!cmp_bytes_alloc(&bytes, in_length)) {FcStrSetDestroy(decoded); goto fail;}
    bool ok = encodeStrSet(&bytes, decoded);
    FcStrSetDestroy(decoded);
    if (ok) return cmp_bytes_take(&bytes, length);
    else {
fail:
        cmp_bytes_free(&bytes);
        return NULL;
    }
}

FcCharSet *decodeObjCharSet(cmp_ctx_t *bytes, cmp_object_t *head, cmp_object_t *first) {
    if (bytes == NULL || head == NULL) return NULL;

    int8_t type; uint32_t size;
    if (cmp_object_as_ext(head, &type, &size) && type == 'c' && size == 0)
        return FcCharSetCreate(); // Special unambiguous empty encoding!
    else if (!cmp_object_as_array(head, &size)) return NULL;

    FcCharSet *ret = FcCharSetCreate();
    if (ret == NULL) return NULL;

    FcChar32 prev = 0;
    /* Allow peek-ahead to different based on type inside list */
    if (size > 0 && first != NULL) {
        if (!cmp_object_as_uint(first, &prev)) goto fail;
        if (!FcCharSetAddChar(ret, prev)) goto fail;
        size--; // We've read the first entry of the array!
    }

    /* The proper decoding! Diff-compressed to minimize temp-memory used. */
    for (uint32_t i = 0; i < size; i++) {
        uint32_t x;
        if (!cmp_read_uint(bytes, &x)) goto fail;
        prev += x;
        if (!FcCharSetAddChar(ret, prev)) goto fail;
    }
    return ret;
fail:
    FcCharSetDestroy(ret);
    return NULL;
}

FcCharSet *decodeCharSet(cmp_ctx_t *bytes) {
    if (bytes == NULL) return NULL;

    cmp_object_t head;
    if (!cmp_read_object(bytes, &head)) return NULL;
    return decodeObjCharSet(bytes, &head, NULL);
}

bool encodeCharSet(cmp_ctx_t *bytes, const FcCharSet *data) {
    if (bytes == NULL || data == NULL) return false;

    FcChar32 size = FcCharSetCount(data);
    if (size == 0) { // Unambiguous empty encoding!
        return cmp_write_ext_marker(bytes, 'c', 0);
    }
    FcChar32 count = 0; // For validation
    if (!cmp_write_array(bytes, size)) return false;

    FcChar32 map[FC_CHARSET_MAP_SIZE];
    FcChar32 next;
    FcChar32 base = FcCharSetFirstPage(data, map, &next);
    FcChar32 prev = 0;
    while (base != FC_CHARSET_DONE) {
        for (unsigned int i = 0; i < FC_CHARSET_MAP_SIZE; i++) for (unsigned int j = 0; j < 32; j++) {
            if (map[i] & (1 << j)) {
                FcChar32 c = base + i*32 + j;
                if (!cmp_write_uinteger(bytes, c - prev)) return false;
                prev = c;
                count++;
            }
        }
        base = FcCharSetNextPage(data, map, &next);
    }
    //assert(size == count);
    return true;
}

uint8_t *testCharSet(uint8_t *in, size_t in_length, size_t *length) {
    if (in == NULL) return NULL;

    cmp_ctx_t bytes;
    if (!cmp_bytes_init(&bytes, in, in_length)) return false;
    FcCharSet *decoded = decodeCharSet(&bytes);
    cmp_bytes_take(&bytes, NULL);
    if (decoded == NULL) {return NULL;}

    if (!cmp_bytes_alloc(&bytes, in_length)) {
        FcCharSetDestroy(decoded);
        goto fail;
    }
    bool ok = encodeCharSet(&bytes, decoded);
    FcCharSetDestroy(decoded);
    if (ok) return cmp_bytes_take(&bytes, length);
    else {
fail:
        cmp_bytes_free(&bytes);
        return NULL;
    }
}

FcLangSet *decodeObjLangSet(cmp_ctx_t *bytes, cmp_object_t *head, cmp_object_t *first) {
    uint32_t size = 0;
    if (bytes == NULL || head == NULL || !cmp_object_as_array(head, &size)) return NULL;

    FcLangSet *ret = FcLangSetCreate();
    if (ret == NULL) return NULL;

    /* Allow peek ahead to differentiate based on type stored in list */
    if (first != NULL && size > 0) {
        #define LANG_BUF_SZ 32 // If I'm reading FontConfig code right, 16's enough, but be generous...
        char buf[LANG_BUF_SZ];
        if (!cmp_object_to_str(bytes, first, buf, LANG_BUF_SZ)) goto fail;
        char *lang = FcLangNormalize(buf);
        if (lang == NULL) goto fail;
        if (!FcLangSetAdd(ret, lang)) {free(lang); goto fail;}
        free(lang);
        size--;
    }

    /* The proper decoding! */
    for (uint32_t i = 0; i < size; i++) {
        char lang[10];
        uint32_t str_size = 10;
        if (!cmp_read_str(bytes, lang, &str_size)) goto fail;
        char *lang2 = FcLangNormalize(lang);
        if (lang2 == NULL) goto fail;
        if (!FcLangSetAdd(ret, lang2)) {free(lang2); goto fail;}
        free(lang2);
    }
    return ret;
fail:
    FcLangSetDestroy(ret);
    return NULL;
}

FcLangSet *decodeLangSet(cmp_ctx_t *bytes) {
    if (bytes == NULL) return NULL;
    cmp_object_t head;
    if (!cmp_read_object(bytes, &head)) return NULL;
    return decodeObjLangSet(bytes, &head, NULL);
}

bool encodeLangSet(cmp_ctx_t *bytes, const FcLangSet *data) {
    if (bytes == NULL || data == NULL) return false;
    FcStrSet *langs = FcLangSetGetLangs(data);
    if (langs == NULL) return false;
    bool ret = encodeStrSet(bytes, langs);
    FcStrSetDestroy(langs);
    return ret;
}

uint8_t *testLangSet(uint8_t *in, size_t in_length, size_t *length) {
    if (in == NULL) return NULL;

    cmp_ctx_t bytes;
    if (!cmp_bytes_init(&bytes, in, in_length)) return false;
    FcLangSet *decoded = decodeLangSet(&bytes);
    cmp_bytes_take(&bytes, NULL);
    if (decoded == NULL) return NULL;

    if (!cmp_bytes_alloc(&bytes, in_length)) {
        FcLangSetDestroy(decoded);
        goto fail;
    }
    bool ok = encodeLangSet(&bytes, decoded);
    FcLangSetDestroy(decoded);
    if (ok) return cmp_bytes_take(&bytes, length);
    else {
fail:
        cmp_bytes_free(&bytes);
        return NULL;
    }
}

FcObjectSet *decodeObjectSet(cmp_ctx_t *bytes) {
    if (bytes == NULL) return NULL;

    uint32_t size;
    if (!cmp_read_array(bytes, &size)) return NULL;

    FcObjectSet *ret = FcObjectSetCreate();
    for (uint32_t i = 0; i < size; i++) {
        char object[20];
        uint32_t o_size = 20;
        if (!cmp_read_str(bytes, object, &o_size)) goto fail;
        if (!FcObjectSetAdd(ret, object)) goto fail;
    }
    return ret;
fail:
    FcObjectSetDestroy(ret);
    return NULL;
}

// No corresponding objectset encoder.

FcRange *decodeObjRange(cmp_ctx_t *bytes, cmp_object_t *head) {
    uint32_t size = 0;
    if (bytes == NULL || head == NULL || !cmp_object_as_map(head, &size)) return NULL;

    double range[2];
    for (uint32_t i = 0; i < size; i++) {
        uint32_t j;
        if (!cmp_read_uint(bytes, &j)) return NULL;
        if (!cmp_read_double(bytes, &range[j])) return NULL;
    }
    return FcRangeCreateDouble(range[0], range[1]);
}

FcRange *decodeRange(cmp_ctx_t *bytes) {
    if (bytes == NULL) return NULL;

    cmp_object_t head;
    if (!cmp_read_object(bytes, &head)) return NULL;
    return decodeObjRange(bytes, &head);
}

bool encodeRange(cmp_ctx_t *bytes, const FcRange *data) {
    if (bytes == NULL || data == NULL) return false;

    double begin, end;
    if (!FcRangeGetDouble(data, &begin, &end)) return false;

    if (!cmp_write_map(bytes, 2)) return false;
    if (!cmp_write_uint(bytes, 0)) return false;
    if (!cmp_write_double(bytes, begin)) return false;
    if (!cmp_write_uint(bytes, 1)) return false;
    if (!cmp_write_double(bytes, end)) return false;
    return true;
}

uint8_t *testRange(uint8_t *in, size_t in_length, size_t *length) {
    if (in == NULL) return NULL;

    cmp_ctx_t bytes;
    if (!cmp_bytes_init(&bytes, in, in_length)) return false;
    FcRange *decoded = decodeRange(&bytes);
    cmp_bytes_take(&bytes, NULL);
    if (decoded == NULL) return NULL;

    if (!cmp_bytes_alloc(&bytes, in_length)) {FcRangeDestroy(decoded); goto fail;}
    bool ok = encodeRange(&bytes, decoded);
    FcRangeDestroy(decoded);
    if (ok) return cmp_bytes_take(&bytes, length);
    else {
fail:
        cmp_bytes_free(&bytes);
        return NULL;
    }
}

FcMatrix *decodeObjMatrix(cmp_ctx_t *bytes, cmp_object_t *head, cmp_object_t *next) {
    uint32_t size = 0;
    if (bytes == NULL || head == NULL) return NULL;
    if (!cmp_object_as_array(head, &size) || size != 4) return NULL;

    cmp_object_t first;
    if (next == NULL) {
        if (!cmp_read_object(bytes, &first)) return NULL;
    } else first = *next;
    if (!cmp_object_is_double(&first)) return NULL;

    FcMatrix *ret = malloc(sizeof(FcMatrix));
    if (ret == NULL) return NULL;
    if (!cmp_object_as_double(&first, &ret->xx)) goto fail;
    if (!cmp_read_double(bytes, &ret->xy)) goto fail;
    if (!cmp_read_double(bytes, &ret->yx)) goto fail;
    if (!cmp_read_double(bytes, &ret->yy)) goto fail;
    return ret;
fail:
    free(ret);
    return NULL;
}

FcMatrix *decodeMatrix(cmp_ctx_t *bytes) {
    if (bytes == NULL) return NULL;

    cmp_object_t head;
    if (!cmp_read_object(bytes, &head)) return NULL;
    return decodeObjMatrix(bytes, &head, NULL);
}

bool encodeMatrix(cmp_ctx_t *bytes, const FcMatrix *data) {
    if (bytes == NULL || data == NULL) return NULL;

    if (!cmp_write_array(bytes, 4)) return false;
    if (!cmp_write_double(bytes, data->xx)) return false;
    if (!cmp_write_double(bytes, data->xy)) return false;
    if (!cmp_write_double(bytes, data->yx)) return false;
    if (!cmp_write_double(bytes, data->yy)) return false;
    return true;
}

uint8_t *testMatrix(uint8_t *in, size_t in_length, size_t *length) {
    if (in == NULL) return NULL;

    cmp_ctx_t bytes;
    if (!cmp_bytes_init(&bytes, in, in_length)) return false;
    FcMatrix *decoded = decodeMatrix(&bytes);
    cmp_bytes_take(&bytes, NULL);
    if (decoded == NULL) return NULL;

    if (!cmp_bytes_alloc(&bytes, in_length)) {free(decoded); goto fail;}
    bool ok = encodeMatrix(&bytes, decoded);
    free(decoded);
    if (ok) return cmp_bytes_take(&bytes, length);
    else {
fail:
        cmp_bytes_free(&bytes);
        return NULL;
    }
}

bool decodeValue(cmp_ctx_t *bytes, FcValue *out) {
    if (bytes == NULL || out == NULL) return false;

    cmp_object_t head;
    if (!cmp_read_object(bytes, &head)) return NULL;
    bool b;
    uint32_t size;
    if (head.type == CMP_TYPE_NIL) out->type = FcTypeVoid;
    else if (cmp_object_as_int(&head, &out->u.i)) out->type = FcTypeInteger;
    else if (cmp_object_as_double(&head, &out->u.d)) out->type = FcTypeDouble;
    else if (cmp_object_as_str(&head, &size)) {
        out->type = FcTypeString;
        size++; // Include space for Nil byte!
        char *str = malloc(size);
        out->u.s = str;
        bool ok = cmp_object_to_str(bytes, &head, str, size);
        if (!ok) free(str);
        return ok;
    } else if (cmp_object_as_bool(&head, &b)) {
        out->type = FcTypeBool;
        out->u.b = b ? FcTrue : FcFalse; // Didn't auto-convert.
    } else if (cmp_object_as_array(&head, &size)) {
        cmp_object_t *first = NULL;
        cmp_object_t first_;
        if (size > 0) {
            first = &first_;
            if (!cmp_read_object(bytes, first)) return false;
        }
        if ((out->u.m = decodeObjMatrix(bytes, &head, first)) != NULL)
            out->type = FcTypeMatrix;
        // For ease of encoding empty lists are treated as lang sets.
        // Hence LangSets take priority during decode!
        else if ((out->u.l = decodeObjLangSet(bytes, &head, first)) != NULL)
            out->type = FcTypeLangSet;
        else if ((out->u.c = decodeObjCharSet(bytes, &head, first)) != NULL)
            out->type = FcTypeCharSet;
    } // Not supporting FcTypeFcFace
    else if ((out->u.r = decodeObjRange(bytes, &head)) != NULL)
        out->type = FcTypeRange;
    else return false;
    return true;
}

bool encodeValue(cmp_ctx_t *bytes, FcValue *data) {
    if (bytes == NULL || data == NULL) return false;

    switch (data->type) {
    case FcTypeVoid:
        return cmp_write_nil(bytes);
    case FcTypeInteger:
        return cmp_write_int(bytes, data->u.i);
    case FcTypeDouble:
        return cmp_write_double(bytes, data->u.d);
    case FcTypeString:
        return cmp_write_str(bytes, data->u.s, strlen(data->u.s));
    case FcTypeBool:
        return cmp_write_bool(bytes, data->u.b);
    case FcTypeMatrix:
        return encodeMatrix(bytes, data->u.m);
    case FcTypeCharSet:
        return encodeCharSet(bytes, data->u.c);
    case FcTypeFTFace:
        return true; // Not supporting this yet...
    case FcTypeLangSet:
        return encodeLangSet(bytes, data->u.l);
    case FcTypeRange:
        return encodeRange(bytes, data->u.r);
    default:
        return false;
    }
}

uint8_t *testValue(uint8_t *in, size_t in_length, size_t *length) {
    if (in == NULL) return NULL;

    cmp_ctx_t bytes;
    if (!cmp_bytes_init(&bytes, in, in_length)) return false;
    FcValue decoded;
    bool ok = decodeValue(&bytes, &decoded);
    cmp_bytes_take(&bytes, NULL);
    if (!ok) return NULL;

    if (!cmp_bytes_alloc(&bytes, in_length)) {FcValueDestroy(decoded); goto fail;}
    ok = encodeValue(&bytes, &decoded);
    FcValueDestroy(decoded);
    if (ok) return cmp_bytes_take(&bytes, length);
    else {
fail:
        cmp_bytes_free(&bytes);
        return NULL;
    }
}

FcPattern *decodePattern(cmp_ctx_t *bytes) {
    if (bytes == NULL) return NULL;

    uint32_t size;
    if (!cmp_read_map(bytes, &size)) return NULL;

    FcPattern *ret = FcPatternCreate();
    if (ret == NULL) return NULL;
    for (uint32_t i = 0; i < size; i++) {
        char object[20];
        uint32_t osize = 20;
        if (!cmp_read_str(bytes, object, &osize)) goto fail;
        uint32_t vsize;
        if (!cmp_read_array(bytes, &vsize)) goto fail;
        for (uint32_t j = 0; j < vsize; j++) {
            uint32_t tsize;
            if (!cmp_read_array(bytes, &tsize) || tsize != 2) goto fail;

            cmp_object_t strength;
            if (!cmp_read_object(bytes, &strength)) goto fail;
            bool is_strong = false;
            if (cmp_object_as_bool(&strength, &is_strong)) {}
            else if (cmp_object_is_nil(&strength)) {}
            else goto fail;

            FcValue val;
            if (!decodeValue(bytes, &val)) goto fail;
            //FcValuePrint(val);
            if (is_strong) {
                if (!FcPatternAdd(ret, object, val, true)) {FcValuePrint(val); goto fail;}
            } else {
                if (!FcPatternAddWeak(ret, object, val, true)) {FcValuePrint(val); goto fail;}
            }
            FcValueDestroy(val);
        }
    }
    return ret;
fail:
    FcPatternDestroy(ret);
    return NULL;
}

bool encodePattern(cmp_ctx_t *bytes, FcPattern *data) {
    if (bytes == NULL || data == NULL) return false;

    int size = FcPatternObjectCount(data);
    if (!cmp_write_map(bytes, size)) return false;

    FcPatternIter iter;
    FcPatternIterStart(data, &iter);
    int count = 0;
    do {
        count++;
        const char *obj = FcPatternIterGetObject(data, &iter);
        if (!cmp_write_str(bytes, obj, strlen(obj))) return false;
        int nvalues = FcPatternIterValueCount(data, &iter);
        if (!cmp_write_array(bytes, nvalues)) return false;
        for (int j = 0; j < nvalues; j++) {
            FcValue val;
            FcValueBinding weight;
            if (FcPatternIterGetValue(data, &iter, j, &val, &weight) != FcResultMatch)
                return false;

            if (!cmp_write_array(bytes, 2)) return false;
            switch (weight) {
            case FcValueBindingWeak:
                if (!cmp_write_bool(bytes, false)) return false;
                break;
            case FcValueBindingStrong:
                if (!cmp_write_bool(bytes, true)) return false;
                break;
            case FcValueBindingSame:
                if (!cmp_write_nil(bytes)) return false;
                break;
            default:
                return false;
            }
            if (!encodeValue(bytes, &val)) return false;
        }
    } while (FcPatternIterNext(data, &iter));
    assert(size == count);
    return true;
}

uint8_t *testPattern(uint8_t *in, size_t in_length, size_t *length) {
    if (in == NULL) return NULL;

    cmp_ctx_t bytes;
    if (!cmp_bytes_init(&bytes, in, in_length)) return false;
    FcPattern *decoded = decodePattern(&bytes);
    cmp_bytes_take(&bytes, NULL);
    if (decoded == NULL) return NULL;

    if (!cmp_bytes_alloc(&bytes, in_length)) {FcPatternDestroy(decoded); goto fail;}
    bool ok = encodePattern(&bytes, decoded);
    FcPatternDestroy(decoded);
    if (ok) return cmp_bytes_take(&bytes, length);
    else {
fail:
        cmp_bytes_free(&bytes);
        return NULL;
    }
}

FcFontSet *decodeFontSet(cmp_ctx_t *bytes) {
    if (bytes == NULL) return NULL;

    uint32_t size;
    if (!cmp_read_array(bytes, &size)) return NULL;

    FcFontSet *ret = FcFontSetCreate();
    if (ret == NULL) return NULL;

    for (uint32_t i = 0; i < size; i++) {
        FcPattern *font = decodePattern(bytes);
        if (font == NULL) goto fail;
        if (!FcFontSetAdd(ret, font)) goto fail;
    }
    return ret;
fail:
    FcFontSetDestroy(ret);
    return NULL;
}

FcFontSet **decodeFontSets(cmp_ctx_t *bytes, size_t *nsets) {
    if (bytes == NULL) return NULL;

    uint32_t size;
    if (!cmp_read_array(bytes, &size)) return NULL;
    if (nsets != NULL) *nsets = size;

    FcFontSet **ret = calloc(sizeof(FcFontSet *), size);
    uint32_t i;
    for (i = 0; i < size; i++) {
        ret[i] = decodeFontSet(bytes);
        if (ret[i] == NULL) goto fail;
        i++;
    }
    return ret;
fail:
    for (uint32_t j = 0; j < i; j++) FcFontSetDestroy(ret[j]);
    free(ret);
    return NULL;
}

bool encodeFontSet(cmp_ctx_t *bytes, FcFontSet *data) {
    if (bytes == NULL || data == NULL) return NULL;

    if (!cmp_write_array(bytes, data->nfont)) return false;
    for (int i = 0; i < data->nfont; i++) {
        if (!encodePattern(bytes, data->fonts[i])) return false;
    }
    return true;
}

uint8_t *testFontSet(uint8_t *in, size_t in_length, size_t *length) {
    if (in == NULL) return NULL;

    cmp_ctx_t bytes;
    if (!cmp_bytes_init(&bytes, in, in_length)) return false;
    FcFontSet *decoded = decodeFontSet(&bytes);
    cmp_bytes_take(&bytes, NULL);
    if (decoded == NULL) return NULL;

    if (!cmp_bytes_alloc(&bytes, in_length)) {
        FcFontSetDestroy(decoded);
        goto fail;
    }
    bool ok = encodeFontSet(&bytes, decoded);
    FcFontSetDestroy(decoded);
    if (ok) return cmp_bytes_take(&bytes, length);
    else {
fail:
        cmp_bytes_free(&bytes);
        return NULL;
    }
}

bool encodeRenderableFontSet(cmp_ctx_t *bytes, FcConfig *conf, FcPattern *pat, FcFontSet *data) {
    if (bytes == NULL || data == NULL) return false;

    if (!cmp_write_array(bytes, data->nfont)) return false;
    for (int i = 0; i < data->nfont; i++) {
        FcPattern *postprocessed = FcFontRenderPrepare(conf, pat, data->fonts[i]);
        if (postprocessed == NULL) return false;

        bool ok = encodePattern(bytes, postprocessed);
        FcPatternDestroy(postprocessed);
        if (!ok) return false;
    }
    return true;
}

bool encodeResult(cmp_ctx_t *bytes, FcResult res) {
    switch (res) {
    case FcResultMatch: // Should be handled by caller! Can't do anything sensible here.
    case FcResultNoMatch: // May be handled by caller.
        return cmp_write_nil(bytes);
    case FcResultTypeMismatch:
        return cmp_write_str(bytes, "ErrType", strlen("ErrType"));
    case FcResultNoId:
        return cmp_write_str(bytes, "ErrNoId", strlen("ErrNoId"));
    case FcResultOutOfMemory:
        return cmp_write_str(bytes, "ErrOOM", strlen("ErrOOM"));
    default:
        // Should never happen!
        return cmp_write_str(bytes, "ErrOther", strlen("ErrOther"));
    }
}