~alcinnz/fontconfig-pure

58463bff4ead31b6b58f5e6b181914c856a33537 — Adrian Cochrane 6 months ago c406e20
Fuzz test roundtrip conversions between C & Haskell datastructures!
M cbits/transcode.c => cbits/transcode.c +367 -113
@@ 3,6 3,72 @@
#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;


@@ 72,21 138,46 @@ bool encodeStrSet(cmp_ctx_t *bytes, FcStrSet *data) {
    return ret;
}

FcCharSet *decodeCharSet(cmp_ctx_t *bytes) {
    if (bytes == NULL) return NULL;
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;

    int8_t type = 'c'; uint32_t size = 0;
    // Special unambiguous empty encoding!
    if (cmp_read_ext_marker(bytes, &type, &size) && type == 'c' && size == 0) {
        return FcCharSetCreate();
    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;
    }
}

    if (!cmp_read_array(bytes, &size)) 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;


@@ 99,6 190,14 @@ fail:
    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;



@@ 111,32 210,73 @@ bool encodeCharSet(cmp_ctx_t *bytes, const FcCharSet *data) {

    FcChar32 map[FC_CHARSET_MAP_SIZE];
    FcChar32 next;
    FcChar32 c = FcCharSetFirstPage(data, map, &next);
    FcChar32 base = FcCharSetFirstPage(data, map, &next);
    FcChar32 prev = 0;
    while (c != FC_CHARSET_DONE) {
        if (!cmp_write_uinteger(bytes, c - prev)) return false;
        prev = c;
        count++;
        c = FcCharSetNextPage(data, map, &next);
    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);
    //assert(size == count);
    return true;
}

FcLangSet *decodeLangSet(cmp_ctx_t *bytes) {
    if (bytes == NULL) return NULL;
    uint32_t size;
    if (!cmp_read_array(bytes, &size)) return NULL;
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)) goto fail;
        if (!FcLangSetAdd(ret, lang2)) {free(lang2); goto fail;}
        free(lang2);
    }
    return ret;


@@ 145,6 285,13 @@ fail:
    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);


@@ 154,6 301,29 @@ bool encodeLangSet(cmp_ctx_t *bytes, const FcLangSet *data) {
    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;



@@ 175,11 345,9 @@ fail:

// No corresponding objectset encoder.

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

    uint32_t size;
    if (!cmp_read_map(bytes, &size)) return NULL;
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++) {


@@ 190,6 358,14 @@ FcRange *decodeRange(cmp_ctx_t *bytes) {
    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;



@@ 204,14 380,40 @@ bool encodeRange(cmp_ctx_t *bytes, const FcRange *data) {
    return true;
}

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

    uint32_t size;
    if (!cmp_read_array(bytes, &size) || size != 4) 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 (!cmp_read_double(bytes, &ret->xx)) goto fail;
    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;


@@ 221,6 423,14 @@ fail:
    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;



@@ 232,29 442,65 @@ bool encodeMatrix(cmp_ctx_t *bytes, const FcMatrix *data) {
    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 str_size;
    if (cmp_read_nil(bytes)) out->type = FcTypeVoid;
    else if (cmp_read_int(bytes, &out->u.i)) out->type = FcTypeInteger;
    else if (cmp_read_double(bytes, &out->u.d)) out->type = FcTypeDouble;
    else if (cmp_read_str_size(bytes, &str_size)) {
    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;
        char *str = malloc(str_size);
        size++; // Include space for Nil byte!
        char *str = malloc(size);
        out->u.s = str;
        return cmp_read_str(bytes, str, &str_size);
    } else if (cmp_read_bool(bytes, &b)) {
        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 ((out->u.m = decodeMatrix(bytes)) != NULL) out->type = FcTypeMatrix;
    // For ease of encoding lists are treated as lang sets.
    // Hence LangSets take priority during decode!
    else if ((out->u.l = decodeLangSet(bytes)) != NULL) out->type = FcTypeLangSet;
    else if ((out->u.c = decodeCharSet(bytes)) != NULL) out->type = FcTypeCharSet;
    // Not supporting FcTypeFcFace
    else if ((out->u.r = decodeRange(bytes)) != NULL) out->type = FcTypeRange;
    } 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;
}


@@ 288,6 534,27 @@ bool encodeValue(cmp_ctx_t *bytes, FcValue *data) {
    }
}

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;



@@ 295,6 562,7 @@ FcPattern *decodePattern(cmp_ctx_t *bytes) {
    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;


@@ 304,17 572,23 @@ FcPattern *decodePattern(cmp_ctx_t *bytes) {
        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_read_bool(bytes, &is_strong)) {}
            else if (cmp_read_nil(bytes)) {}
            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)) goto fail;
                if (!FcPatternAdd(ret, object, val, true)) {FcValuePrint(val); goto fail;}
            } else {
                if (!FcPatternAddWeak(ret, object, val, true)) goto fail;
                if (!FcPatternAddWeak(ret, object, val, true)) {FcValuePrint(val); goto fail;}
            }
            FcValueDestroy(val);
        }
    }
    return ret;


@@ 332,7 606,7 @@ bool encodePattern(cmp_ctx_t *bytes, FcPattern *data) {
    FcPatternIter iter;
    FcPatternIterStart(data, &iter);
    int count = 0;
    while (FcPatternIterNext(data, &iter)) {
    do {
        count++;
        const char *obj = FcPatternIterGetObject(data, &iter);
        if (!cmp_write_str(bytes, obj, strlen(obj))) return false;


@@ 360,11 634,31 @@ bool encodePattern(cmp_ctx_t *bytes, FcPattern *data) {
            }
            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;



@@ 416,6 710,29 @@ bool encodeFontSet(cmp_ctx_t *bytes, FcFontSet *data) {
    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;



@@ 447,66 764,3 @@ bool encodeResult(cmp_ctx_t *bytes, FcResult res) {
        return cmp_write_str(bytes, "ErrOther", strlen("ErrOther"));
    }
}

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;
}

M cbits/transcode.h => cbits/transcode.h +8 -0
@@ 4,23 4,31 @@
FcStrSet *decodeStrSet(cmp_ctx_t *bytes);
FcStrSet *decodeFileSet(cmp_ctx_t *bytes);
bool encodeStrSet(cmp_ctx_t *bytes, FcStrSet *data);
uint8_t *testStrSet(uint8_t *in, size_t in_length, size_t *length);
FcCharSet *decodeCharSet(cmp_ctx_t *bytes);
bool encodeStrList(cmp_ctx_t *bytes, const FcStrList *data);
bool encodeCharSet(cmp_ctx_t *bytes, const FcCharSet *data);
uint8_t *testCharSet(uint8_t *in, size_t in_length, size_t *length);
FcLangSet *decodeLangSet(cmp_ctx_t *bytes);
bool encodeLangSet(cmp_ctx_t *bytes, const FcLangSet *data);
uint8_t *testLangSet(uint8_t *in, size_t in_length, size_t *length);
FcObjectSet *decodeObjectSet(cmp_ctx_t *bytes);
FcRange *decodeRange(cmp_ctx_t *bytes);
bool encodeRange(cmp_ctx_t *bytes, const FcRange *data);
uint8_t *testRange(uint8_t *in, size_t in_length, size_t *length);
FcMatrix *decodeMatrix(cmp_ctx_t *bytes);
bool encodeMatrix(cmp_ctx_t *bytes, const FcMatrix *data);
uint8_t *testMatrix(uint8_t *in, size_t in_length, size_t *length);
bool decodeValue(cmp_ctx_t *bytes, FcValue *out);
bool encodeValue(cmp_ctx_t *bytes, FcValue *data);
uint8_t *testValue(uint8_t *in, size_t in_length, size_t *length);
FcPattern *decodePattern(cmp_ctx_t *bytes);
bool encodePattern(cmp_ctx_t *bytes, FcPattern *data);
uint8_t *testPattern(uint8_t *in, size_t in_length, size_t *length);
FcFontSet *decodeFontSet(cmp_ctx_t *bytes);
FcFontSet **decodeFontSets(cmp_ctx_t *bytes, size_t *nsets);
bool encodeFontSet(cmp_ctx_t *bytes, FcFontSet *data);
uint8_t *testFontSet(uint8_t *in, size_t in_length, size_t *length);
bool encodeRenderableFontSet(cmp_ctx_t *bytes, FcFontSet *data);
bool encodeResult(cmp_ctx_t *bytes, FcResult res);


M fontconfig-pure.cabal => fontconfig-pure.cabal +2 -2
@@ 70,7 70,7 @@ library
            Graphics.Text.Font.Choose.Config, Graphics.Text.Font.Choose.Result,
            Graphics.Text.Font.Choose.Internal.FFI, FreeType.FontConfig,
            Graphics.Text.Font.Choose.Config.Accessors, Graphics.Text.Font.Choose.Weight,
            Graphics.Text.Font.Choose
            Graphics.Text.Font.Choose.Internal.Test, Graphics.Text.Font.Choose

    c-sources: cbits/cmp.c, cbits/transcode.c, cbits/fontconfig-wrap.c
    include-dirs: cbits


@@ 147,4 147,4 @@ test-suite fontconfig-pure-test
        base ^>=4.17.0.0,
        fontconfig-pure,
        hspec, QuickCheck,
        msgpack
        msgpack, containers, text

M lib/Graphics/Text/Font/Choose.hs => lib/Graphics/Text/Font/Choose.hs +3 -3
@@ 1,15 1,15 @@
module Graphics.Text.Font.Choose(
        module Graphics.Text.Font.Choose.Config.Accessors, Config', fini, version,
        initLoadConfig, initLoadConfigAndFonts, initFonts, reinit, bringUptoDate,
        CharSet, ord, chr, parseCharSet, CharSet'(..),
        CharSet, ord, chr, parseCharSet, CharSet'(..), validCharSet',
        module Graphics.Text.Font.Choose.FontSet,
        module Graphics.Text.Font.Choose.LangSet,
        module Graphics.Text.Font.Choose.ObjectSet,
        Pattern, Pattern'(..), Binding(..),
        Pattern, Pattern'(..), Binding(..), validPattern, validPattern',
        setValue, setValues, getValue, getValues, equalSubset, defaultSubstitute,
        nameParse, nameUnparse, nameFormat,
        module Graphics.Text.Font.Choose.Range,
        FcException(..), StrSet(..),
        FcException(..), StrSet(..), validStrSet,
        module Graphics.Text.Font.Choose.Value,
        module Graphics.Text.Font.Choose.Weight
    ) where

M lib/Graphics/Text/Font/Choose/CharSet.hs => lib/Graphics/Text/Font/Choose/CharSet.hs +7 -2
@@ 1,5 1,6 @@
module Graphics.Text.Font.Choose.CharSet(
        CharSet, ord, chr, module IntSet, parseCharSet, CharSet'(..)) where
        CharSet, ord, chr, module IntSet, parseCharSet, CharSet'(..), validCharSet'
    ) where

import Data.IntSet (IntSet, union)
import qualified Data.IntSet as IntSet


@@ 59,4 60,8 @@ instance MessagePack CharSet' where
    fromObject msg =
        CharSet' <$> IntSet.fromAscList <$> diffDecompress 0 <$> fromObject msg
instance Arbitrary CharSet' where
    arbitrary = CharSet' <$> arbitrary
    arbitrary = CharSet' <$> IntSet.fromList <$> map (succ . abs) <$> arbitrary

validCharSet' :: CharSet' -> Bool
validCharSet' (CharSet' self) =
    not (IntSet.null self) && all (> 0) (IntSet.toList self)

M lib/Graphics/Text/Font/Choose/FontSet.hs => lib/Graphics/Text/Font/Choose/FontSet.hs +5 -1
@@ 1,6 1,7 @@
{-# LANGUAGE CApiFFI, OverloadedStrings #-}
module Graphics.Text.Font.Choose.FontSet(
        FontSet, fontSetList, fontSetMatch, fontSetSort, FontFaceParser(..)) where
        FontSet, validFontSet, fontSetList, fontSetMatch, fontSetSort, FontFaceParser(..)
    ) where

import Graphics.Text.Font.Choose.Pattern hiding (map)
import Graphics.Text.Font.Choose.Config


@@ 24,6 25,9 @@ import Graphics.Text.Font.Choose.Value (ToValue(..), Value)

type FontSet = [Pattern]

validFontSet :: FontSet -> Bool
validFontSet = all validPattern

fontSetList :: Config -> [FontSet] -> Pattern -> ObjectSet -> FontSet
fontSetList a b c d =
    fromMessage0 $ arg d $ arg c $ arg b $ withForeignPtr' fcFontSetList a

M lib/Graphics/Text/Font/Choose/LangSet.hs => lib/Graphics/Text/Font/Choose/LangSet.hs +9 -4
@@ 1,14 1,14 @@
{-# LANGUAGE CApiFFI #-}
module Graphics.Text.Font.Choose.LangSet(
        LangSet, LangSet'(..), module S, LangComparison(..),
        LangSet, LangSet'(..), module S, LangComparison(..), validLangSet, validLangSet',
        cmp, has, defaultLangs, langs, normalize, langCharSet) where

import Data.Set (Set)
import qualified Data.Set as S

import Data.MessagePack (MessagePack(..))
import Test.QuickCheck (Arbitrary(..))
import Graphics.Text.Font.Choose.StrSet (StrSet)
import Test.QuickCheck (Arbitrary(..), elements, listOf)
import Graphics.Text.Font.Choose.StrSet (StrSet(..))
import Graphics.Text.Font.Choose.CharSet (CharSet')

import Foreign.C.String (CString)


@@ 20,11 20,16 @@ import Control.Exception (throw)
type LangSet = Set String
newtype LangSet' = LangSet' { unLangSet :: LangSet } deriving (Eq, Show, Read)

validLangSet :: LangSet -> Bool
validLangSet x = all (`elem` unStrSet langs) x && not (null x)
validLangSet' :: LangSet' -> Bool
validLangSet' = validLangSet . unLangSet

instance MessagePack LangSet' where
    toObject = toObject . S.toList . unLangSet
    fromObject msg = LangSet' <$> S.fromList <$> fromObject msg
instance Arbitrary LangSet' where
    arbitrary = LangSet' <$> arbitrary
    arbitrary = LangSet' <$> S.fromList <$> listOf (elements $ S.toList $ unStrSet langs)

data LangComparison = SameLang | SameTerritory | DifferentLang
i2cmp :: Int -> LangComparison

M lib/Graphics/Text/Font/Choose/Pattern.hs => lib/Graphics/Text/Font/Choose/Pattern.hs +25 -6
@@ 2,13 2,13 @@
{-# LANGUAGE OverloadedStrings #-}
module Graphics.Text.Font.Choose.Pattern(Pattern, Pattern'(..), module M, Binding(..),
        setValue, setValues, getValue, getValues, equalSubset, defaultSubstitute,
        nameParse, nameUnparse, nameFormat,
        nameParse, nameUnparse, nameFormat, validPattern, validPattern',
        -- For Graphics.Text.Font.Choose.FontSet
        parseFontStretch, parseFontWeight, parseFontFeatures, parseFontVars) where

import Data.Map as M
import Data.MessagePack (MessagePack(..), Object(..))
import Test.QuickCheck (Arbitrary(..), chooseEnum)
import Test.QuickCheck (Arbitrary(..), elements)
import Data.Hashable (Hashable(..))
import GHC.Generics (Generic)



@@ 29,8 29,10 @@ import qualified Data.Text as Txt
import Data.Scientific (toRealFloat)
import Data.List (intercalate)
import Data.Maybe as Mb (listToMaybe, fromMaybe, mapMaybe)
import Data.Char (isAscii)
import Prelude as L

type Pattern = Map Text [(Binding, Value)]
type Pattern = M.Map Text [(Binding, Value)]
data Pattern' = Pattern' { unPattern :: Pattern } deriving (Eq, Read, Show, Generic)
data Binding = Strong | Weak | Same deriving (Eq, Ord, Enum, Read, Show, Generic)



@@ 51,9 53,26 @@ instance MessagePack Pattern' where
    toObject = toObject . unPattern

instance Arbitrary Pattern' where
    arbitrary = Pattern' <$> M.mapKeys Txt.pack <$> arbitrary
    -- FIXME: Stop enforcing singletons, without incurring too many invalid patterns!
    arbitrary = Pattern' <$> M.mapKeys normKey <$> M.map (:[]) <$> arbitrary
        where
            normKey = Txt.pack . L.filter (/= '\0') . L.map toAscii . L.take 17
            toAscii :: Char -> Char
            toAscii ch = toEnum $ fromEnum ch `mod` 128
instance Arbitrary Binding where
    arbitrary = chooseEnum (Strong, Same)
    arbitrary = elements [Strong, Weak] -- Same doesn't roundtrip!

validPattern :: Pattern -> Bool
validPattern self = not (M.null self) &&
        all (validValue . snd) (concat $ M.elems self) &&
        all (not . L.null) (M.elems self) &&
        all (not . Txt.null) (M.keys self) &&
        all ((/= Same) . fst) (concat $ M.elems self) &&
        all (not . Txt.elem '\0') (M.keys self) &&
        all (Txt.all isAscii) (M.keys self) &&
        all (\k -> Txt.length k < 18) (M.keys self)
validPattern' :: Pattern' -> Bool
validPattern' = validPattern . unPattern

setValue :: ToValue v => Text -> Binding -> v -> Pattern -> Pattern
setValue key strength v self = setValues key strength [v] self


@@ 225,7 244,7 @@ instance PropertyParser Pattern' where
        | k `elem` ["initial", "normal"] = Pattern' <$> unset' "fontfeatures" self
    longhand _ (Pattern' self) "font-feature-settings" toks
        | (features, True, []) <- parseFontFeatures toks = Pattern' <$>
            set "fontfeatures" Strong (intercalate "," $ Prelude.map fst features) self
            set "fontfeatures" Strong (intercalate "," $ L.map fst features) self

    longhand _ (Pattern' self) "font-variation-settings" [Ident k]
        | k `elem` ["initial", "normal"] = Pattern' <$> unset' "variable" self

M lib/Graphics/Text/Font/Choose/Range.hs => lib/Graphics/Text/Font/Choose/Range.hs +7 -2
@@ 1,5 1,5 @@
{-# LANGUAGE DeriveGeneric #-}
module Graphics.Text.Font.Choose.Range(Range(..), iRange) where
module Graphics.Text.Font.Choose.Range(Range(..), iRange, validRange) where

import Data.MessagePack (MessagePack(..), Object(..))
import Test.QuickCheck (Arbitrary(..))


@@ 24,5 24,10 @@ instance MessagePack Range where
            Just (IM.findWithDefault 0 0 msg' `Range` IM.findWithDefault 0 1 msg')
        | otherwise = Nothing
instance Arbitrary Range where
    arbitrary = uncurry Range <$> arbitrary
    arbitrary = do
        (a, b) <- arbitrary
        return $ Range a $ a + abs b + 1
instance Hashable Range

validRange :: Range -> Bool
validRange (Range start end) = start < end

M lib/Graphics/Text/Font/Choose/StrSet.hs => lib/Graphics/Text/Font/Choose/StrSet.hs +5 -2
@@ 1,4 1,4 @@
module Graphics.Text.Font.Choose.StrSet(StrSet(..), module S) where
module Graphics.Text.Font.Choose.StrSet(StrSet(..), module S, validStrSet) where

import Data.Set (Set)
import qualified Data.Set as S


@@ 12,4 12,7 @@ instance MessagePack StrSet where
    toObject = toObject . S.toList . unStrSet
    fromObject msg = StrSet <$> S.fromList <$> fromObject msg
instance Arbitrary StrSet where
    arbitrary = StrSet <$> arbitrary
    arbitrary = StrSet <$> S.map (filter (/= '\0')) <$> arbitrary

validStrSet :: StrSet -> Bool
validStrSet (StrSet self) = notElem '\0' `all` self

M lib/Graphics/Text/Font/Choose/Value.hs => lib/Graphics/Text/Font/Choose/Value.hs +16 -8
@@ 1,14 1,14 @@
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module Graphics.Text.Font.Choose.Value(Value(..), ToValue(..)) where
module Graphics.Text.Font.Choose.Value(Value(..), validValue, ToValue(..)) where

import Linear.Matrix (M22)
import Linear.V2 (V2(..))
import Graphics.Text.Font.Choose.CharSet (CharSet, CharSet'(..))
import Graphics.Text.Font.Choose.CharSet (CharSet, CharSet'(..), validCharSet')
import qualified Data.IntSet as S
--import FreeType.Core.Base (FT_Face(..))
import Graphics.Text.Font.Choose.LangSet (LangSet, LangSet'(..))
import Graphics.Text.Font.Choose.Range (Range)
import Graphics.Text.Font.Choose.LangSet (LangSet, LangSet'(..), validLangSet)
import Graphics.Text.Font.Choose.Range (Range, validRange)

import Data.MessagePack (MessagePack(..), Object(..))
import Test.QuickCheck (Arbitrary(..), oneof)


@@ 59,19 59,27 @@ instance MessagePack Value where
        | otherwise = Nothing
instance Arbitrary Value where
    arbitrary = oneof [
        return ValueVoid,
        --return ValueVoid,
        ValueInt <$> arbitrary,
        ValueDouble <$> arbitrary,
        ValueString <$> arbitrary,
        ValueString <$> Prelude.filter (/= '\0') <$> arbitrary,
        ValueBool <$> arbitrary,
        do
            (a, b, c, d) <- arbitrary
            return $ ValueMatrix $ V2 (V2 a b) (V2 c d),
        ValueCharSet <$> arbitrary,
        ValueLangSet <$> arbitrary,
        ValueCharSet <$> unCharSet <$> arbitrary,
        ValueLangSet <$> unLangSet <$> arbitrary,
        ValueRange <$> arbitrary
      ]

validValue :: Value -> Bool
validValue (ValueString "") = False
validValue (ValueString x) = '\0' `notElem` x
validValue (ValueCharSet x) = validCharSet' $ CharSet' x
validValue (ValueLangSet x) = validLangSet x
validValue (ValueRange x) = validRange x
validValue _ = True

-- | Coerces compiletime types to runtime types.
class ToValue x where
    toValue :: x -> Value

M test/Main.hs => test/Main.hs +35 -0
@@ 1,10 1,17 @@
{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck

import Data.MessagePack as MP
import qualified Data.Map as M
import qualified Data.Text as Txt

import Graphics.Text.Font.Choose
import Graphics.Text.Font.Choose.Internal.Test

main :: IO ()
main = hspec $ do


@@ 29,3 36,31 @@ main = hspec $ do
                MP.unpack (MP.pack x) `shouldBe` Just (x :: StrSet)
            prop "Value" $ \x ->
                MP.unpack (MP.pack x) `shouldBe` Just (x :: Value)
        describe "through C datastructures" $ do
            prop "StrSet" $ \x -> validStrSet x ==>
                roundtrip testStrSet x `shouldBe` Just (x :: StrSet)
            prop "CharSet" $ \x -> validCharSet' x ==>
                roundtrip testCharSet x `shouldBe` Just (x :: CharSet')
            prop "LangSet" $ \x -> validLangSet' x ==>
                roundtrip testLangSet x `shouldBe` Just (x :: LangSet')
            prop "Range" $ \x -> validRange x ==>
                roundtrip testRange x `shouldBe` Just (x :: Range)
            prop "Matrix" $ \x -> roundtrip testMatrix x `shouldBe`
                    Just (x :: (Double, Double, Double, Double))
            prop "Value" $ \x -> validValue x ==>
                roundtrip testValue x `shouldBe` Just (x :: Value)
            prop "Trivial Pattern" $ \x -> validValue x ==>
                let pat = Pattern' $ M.fromList [("test", [(Strong, x)])]
                in roundtrip testPattern pat `shouldBe` Just pat
            prop "Tuple Pattern" $ \(x, y) -> validValue x && validValue y ==>
                let pat = Pattern' $ M.fromList [("a", [(Strong, x)]), ("b", [(Strong, y)])]
                in roundtrip testPattern pat `shouldBe` Just pat
            let toAscii :: Char -> Char
                toAscii ch = toEnum $ fromEnum ch `mod` 128
            prop "Random-key pattern" $ \x -> all (\y -> toAscii y /= '\0') x ==>
                let pat = Pattern' $ M.fromList [(Txt.pack $ map toAscii $ take 17 x, [(Strong, ValueBool True)])]
                in roundtrip testPattern pat `shouldBe` Just pat
            prop "Pattern" $ \x -> validPattern' x ==>
                roundtrip testPattern x `shouldBe` Just (x :: Pattern')
            prop "FontSet" $ \x -> let y = filter validPattern $ Prelude.map unPattern x
                in validFontSet y ==> roundtrip testFontSet y `shouldBe` Just y