~alcinnz/fontconfig-pure

bea597e765a8a1b1098fa7aba31d109aa8d6e507 — Adrian Cochrane 8 months ago 04e3e46
Write init/finalize & pattern language bindings, haskell-side!
A cbits/fontconfig-wrap.c => cbits/fontconfig-wrap.c +699 -0
@@ 0,0 1,699 @@
#include "transcode.h"
#include <fontconfig/fontconfig.h>
#include <stdlib.h>
#include <fontconfig/fcfreetype.h>

int fcPatternEqualSubset(uint8_t *data, size_t length) {
    cmp_ctx_t in;
    if (!cmp_bytes_init(&in, data, length)) return -1;
    uint32_t size = 0;
    if (!cmp_read_array(&in, &size) || size != 3) {
        cmp_bytes_take(&in, NULL);
        return -1;
    }
    FcPattern *pa = decodePattern(&in);
    if (pa == NULL) {cmp_bytes_take(&in, NULL); return -1;}
    FcPattern *pb = decodePattern(&in);
    if (pb == NULL) {
        cmp_bytes_take(&in, NULL);
        FcPatternDestroy(pa);
        return -1;
    }
    FcObjectSet *os = decodeObjectSet(&in);
    cmp_bytes_take(&in, NULL);

    int ret = -1;
    if (os != NULL) ret = FcPatternEqualSubset(pa, pb, os) ? 1 : 0;

    FcPatternDestroy(pa);
    FcPatternDestroy(pb);
    FcObjectSetDestroy(os);
    return ret;
}

uint8_t *fcDefaultSubstitute(uint8_t *data, size_t in_length, size_t *length) {
    cmp_ctx_t in;
    if (!cmp_bytes_init(&in, data, in_length)) return NULL;
    FcPattern *pat = decodePattern(&in);
    cmp_bytes_take(&in, NULL);
    if (pat == NULL) return NULL;

    FcDefaultSubstitute(pat);

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {FcPatternDestroy(pat); return NULL;}
    if (!encodePattern(&out, pat)) {
        cmp_bytes_free(&out);
        FcPatternDestroy(pat);
        return NULL;
    }
    FcPatternDestroy(pat);
    return cmp_bytes_take(&out, length);
}

uint8_t *fcNameParse(char *name, size_t *length) {
    FcPattern *pat = FcNameParse(name);
    if (pat == NULL) return NULL;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {FcPatternDestroy(pat); return NULL;}
    if (!encodePattern(&out, pat)) {
        FcPatternDestroy(pat); cmp_bytes_free(&out); return NULL;
    }
    FcPatternDestroy(pat);
    return cmp_bytes_take(&out, length);
}

char *fcNameUnparse(uint8_t *data, size_t length) {
    cmp_ctx_t in;
    if (!cmp_bytes_init(&in, data, length)) return NULL;
    FcPattern *pat = decodePattern(&in);
    cmp_bytes_take(&in, NULL); // Caller frees `data`!
    if (pat == NULL) return NULL;

    char *ret = FcNameUnparse(pat);
    FcPatternDestroy(pat);
    return ret;
}

char *fcNameFormat(uint8_t *data, size_t length, char *format) {
    cmp_ctx_t in;
    if (!cmp_bytes_init(&in, data, length)) return NULL;
    FcPattern *pat = decodePattern(&in);
    cmp_bytes_take(&in, NULL); // Caller frees `data`!
    if (pat == NULL) return NULL;

    char *ret = FcPatternFormat(pat, format);
    FcPatternDestroy(pat);
    return ret;
}

uint8_t *fcFontSetList(FcConfig *config, uint8_t *sets, size_t sets_length,
        uint8_t *pat, size_t pat_length, uint8_t *objects, size_t objs_length, size_t *length) {
    cmp_ctx_t in;
    uint8_t *ret = NULL;

    if (!cmp_bytes_init(&in, sets, sets_length)) return NULL;
    size_t nsets;
    FcFontSet **fontsets = decodeFontSets(&in, &nsets);
    cmp_bytes_take(&in, NULL);
    if (fontsets == NULL) return NULL;

    if (!cmp_bytes_init(&in, pat, pat_length)) goto fail_pat;
    FcPattern *pattern = decodePattern(&in);
    cmp_bytes_take(&in, NULL);
    if (pattern == NULL) goto fail_pat;

    if (!cmp_bytes_init(&in, objects, objs_length)) goto fail_objs;
    FcObjectSet *objs = decodeObjectSet(&in);
    cmp_bytes_take(&in, NULL);
    if (objs == NULL) goto fail_objs;

    FcFontSet *res = FcFontSetList(config, fontsets, nsets, pattern, objs);

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) goto fail;
    bool ok = encodeFontSet(&out, res);
    ret = cmp_bytes_take(&out, length);
    if (!ok && ret != NULL) { free(ret); ret = NULL;}

fail:
    FcFontSetDestroy(res);
    FcObjectSetDestroy(objs);
fail_objs:
    FcPatternDestroy(pattern);
fail_pat:
    for (size_t i = 0; i < nsets; i++) FcFontSetDestroy(fontsets[i]);
    free(fontsets);
    return ret;
}


// FcResultMatch, FcResultNoMatch, FcResultTypeMismatch, FcResultNoId, FcResultOutOfMemory

uint8_t *fcFontSetMatch(FcConfig *config, uint8_t *sets, size_t sets_length,
        uint8_t *pat, size_t pat_length, size_t *length) {
    cmp_ctx_t in;
    uint8_t *ret = NULL;

    if (!cmp_bytes_init(&in, sets, sets_length)) return NULL;
    size_t nsets;
    FcFontSet **fontsets = decodeFontSets(&in, &nsets);
    cmp_bytes_take(&in, NULL);
    if (fontsets == NULL) return NULL;

    if (!cmp_bytes_init(&in, pat, pat_length)) goto fail;
    FcPattern *pattern = decodePattern(&in);
    cmp_bytes_take(&in, NULL);
    if (pattern == NULL) goto fail;

    // Necessary preprocessing!
    FcPattern *res = NULL;
    if (!FcConfigSubstitute(config, pattern, FcMatchPattern)) goto fail2;
    FcDefaultSubstitute(pattern);

    FcResult err;
    res = FcFontSetMatch(config, fontsets, nsets, pattern, &err);

    cmp_ctx_t out;
    bool ok;
    if (err == FcResultMatch) {
        if (!cmp_bytes_alloc(&out, 1024)) goto fail2;
        ok = encodePattern(&out, res);
        ret = cmp_bytes_take(&out, length);
        if (!ok && ret != NULL) { free(ret); ret = NULL; }
    } else {
        if (!cmp_bytes_alloc(&out, 32)) goto fail2;
        ok = encodeResult(&out, err);
        ret = cmp_bytes_take(&out, length);
        if (!ok && ret != NULL) { free(ret); ret = NULL; }
    }

fail2:
    if (res != NULL) FcPatternDestroy(res);
    FcPatternDestroy(pattern);
fail:
    for (size_t i = 0; i < nsets; i++) FcFontSetDestroy(fontsets[i]);
    free(fontsets);
    return ret;
}

uint8_t *fcFontSetSort(FcConfig *config, uint8_t *sets, size_t sets_length,
        uint8_t *pat, size_t pat_length, bool trim, size_t *length) {
    cmp_ctx_t in;
    uint8_t *ret = NULL;

    if (!cmp_bytes_init(&in, sets, sets_length)) return NULL;
    size_t nsets;
    FcFontSet **fontsets = decodeFontSets(&in, &nsets);
    cmp_bytes_take(&in, NULL);
    if (fontsets == NULL) return NULL;

    if (!cmp_bytes_init(&in, pat, pat_length)) goto fail;
    FcPattern *pattern = decodePattern(&in);
    cmp_bytes_take(&in, NULL);
    if (pattern == NULL) goto fail;

    // Necessary preprocessing!
    if (!FcConfigSubstitute(config, pattern, FcMatchPattern)) goto fail2;
    FcDefaultSubstitute(pattern);

    FcResult err;
    FcCharSet *charset = NULL;
    FcFontSet *res = FcFontSetSort(config, fontsets, nsets, pattern, trim, &charset, &err);

    cmp_ctx_t out;
    bool ok = true;
    switch (err) {
    case FcResultMatch:
        if (!cmp_bytes_alloc(&out, 1024*res->nfont)) goto fail3;
        ok = ok || cmp_write_array(&out, 2);
        if (res == NULL) cmp_write_nil(&out);
        else {
            // FIXME: Postprocess each font! Rather than call encodeFontSet!
            ok = ok || encodeFontSet(&out, res);
        }
        if (charset == NULL) cmp_write_nil(&out);
        else ok = ok || encodeCharSet(&out, charset);
        break;
    case FcResultNoMatch:
        if (!cmp_bytes_alloc(&out, 1024)) goto fail3;
        ok = ok || cmp_write_array(&out, 2);
        ok = ok || cmp_write_array(&out, 0);
        if (charset == NULL) cmp_write_nil(&out);
        else ok = ok || encodeCharSet(&out, charset);
        break;
    default:
        if (!cmp_bytes_alloc(&out, 32)) goto fail3;
        ok = ok || encodeResult(&out, err);
    }
    ret = cmp_bytes_take(&out, length);
    if (!ok && ret != NULL) { free(ret); ret = NULL; }

fail3:
    FcFontSetDestroy(res);
    if (charset != NULL) FcCharSetDestroy(charset);
fail2:
    FcPatternDestroy(pattern);
fail:
    for (size_t i = 0; i < nsets; i++) FcFontSetDestroy(fontsets[i]);
    free(fontsets);
    return ret;
}

unsigned int fcFreeTypeCharIndex(FT_Face *face, uint32_t ucs4) {
    return FcFreeTypeCharIndex(*face, ucs4);
}

uint8_t *fcFreeTypeCharSet(FT_Face *face, size_t *length) {
    FcCharSet *res = FcFreeTypeCharSet(*face, NULL);

    if (res == NULL) return NULL;
    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {
        FcCharSetDestroy(res);
        return NULL;
    }
    if (!encodeCharSet(&out, res)) {
        FcCharSetDestroy(res);
        cmp_bytes_free(&out);
        return NULL;
    }
    FcCharSetDestroy(res);
    return cmp_bytes_take(&out, length);;
}

uint8_t *fcFreeTypeCharSetAndSpacing(FT_Face *face, size_t *length) {
    int spacing;
    FcCharSet *res = FcFreeTypeCharSetAndSpacing(*face, NULL, &spacing);
    if (res == NULL) return NULL;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {
        FcCharSetDestroy(res);
        return NULL;
    }
    if (!cmp_write_array(&out, 2)) goto fail;
    switch (spacing) { // This branches ensures a consistant ABI for Haskell to bind against.
    case FC_MONO:
        if (!cmp_write_integer(&out, 0)) goto fail;
        break;
    case FC_DUAL:
        if (!cmp_write_integer(&out, 1)) goto fail;
        break;
    case FC_PROPORTIONAL:
        if (!cmp_write_integer(&out, 2)) goto fail;
        break;
    default:
        if (!cmp_write_integer(&out, 3)) goto fail;
        break;
    }
    if (!encodeCharSet(&out, res)) goto fail;
    FcCharSetDestroy(res);
    return cmp_bytes_take(&out, length);;

fail:
    FcCharSetDestroy(res);
    cmp_bytes_free(&out);
    return NULL;
}

uint8_t *fcFreeTypeQuery(char *file, int id, size_t *length) {
    int count;
    FcPattern *res = FcFreeTypeQuery(file, id, NULL, &count);
    if (res == NULL) return NULL;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {
        FcPatternDestroy(res);
        return NULL;
    }
    if (!cmp_write_array(&out, 2)) goto fail;
    if (!cmp_write_integer(&out, count)) goto fail;
    if (!encodePattern(&out, res)) goto fail;
    FcPatternDestroy(res);
    return cmp_bytes_take(&out, length);;

fail:
    FcPatternDestroy(res);
    cmp_bytes_free(&out);
    return NULL;
}

uint8_t *fcFreeTypeQueryAll(char *file, size_t *length) {
    int count;
    FcFontSet *fontset = FcFontSetCreate();
    if (fontset == NULL) return NULL;
    unsigned int npatterns = FcFreeTypeQueryAll(file, -1, NULL, &count, fontset);

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {
        FcFontSetDestroy(fontset);
        return NULL;
    }
    if (!cmp_write_array(&out, 3)) goto fail;
    if (!cmp_write_integer(&out, npatterns)) goto fail;
    if (!cmp_write_integer(&out, count)) goto fail;
    if (!encodeFontSet(&out, fontset)) goto fail;
    uint8_t *ret = cmp_bytes_take(&out, length);
    FcFontSetDestroy(fontset);
    return ret;

fail:
    FcFontSetDestroy(fontset);
    cmp_bytes_free(&out);
    return NULL;
}

uint8_t *fcFreeTypeQueryFace(FT_Face *face, char *file, int id, size_t *length) {
    FcPattern *res = FcFreeTypeQueryFace(*face, file, id, NULL);
    if (res == NULL) return NULL;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {
        FcPatternDestroy(res);
        return NULL;
    }
    if (!encodePattern(&out, res)) {
        FcPatternDestroy(res);
        cmp_bytes_free(&out);
    }
    uint8_t *ret = cmp_bytes_take(&out, length);
    FcPatternDestroy(res);
    return ret;
}

int fcLangSetHasLang(uint8_t *langset, size_t length, const char *lang) {
    cmp_ctx_t in;
    if (!cmp_bytes_init(&in, langset, length)) return -1;
    FcLangSet *ls = decodeLangSet(&in);
    cmp_bytes_take(&in, NULL); // Caller frees `langset`
    if (ls == NULL) return -1;

    FcLangResult ret = FcLangSetHasLang(ls, lang);
    FcLangSetDestroy(ls);
    switch (ret) {
    case FcLangDifferentLang:
        return 0;
    case FcLangEqual:
        return 1;
    case FcLangDifferentTerritory:
        return 2;
    default:
        return -2;
    }
}

uint8_t *fcGetDefaultLangs(size_t *length) {
    FcStrSet *res = FcGetDefaultLangs();
    if (res == NULL) return NULL;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {
        FcStrSetDestroy(res);
        return NULL;
    }
    if (!encodeStrSet(&out, res)) {
        cmp_bytes_free(&out);
        FcStrSetDestroy(res);
        return NULL;
    }
    FcStrSetDestroy(res);
    return cmp_bytes_take(&out, length);
}

uint8_t *fcGetLangs(size_t *length) {
    FcStrSet *res = FcGetLangs();
    if (res == NULL) return NULL;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {
        FcStrSetDestroy(res);
        return NULL;
    }
    if (!encodeStrSet(&out, res)) {
        cmp_bytes_free(&out);
        FcStrSetDestroy(res);
        return NULL;
    }
    FcStrSetDestroy(res);
    return cmp_bytes_take(&out, length);
}

char *fcLangNormalize(char *lang) {return FcLangNormalize(lang);}

uint8_t *fcLangGetCharSet(const char *lang, size_t *length) {
    const FcCharSet *res = FcLangGetCharSet(lang);
    if (res == NULL) return NULL;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) return NULL;
    if (!encodeCharSet(&out, res)) {
        cmp_bytes_free(&out);
        return NULL;
    }
    return cmp_bytes_take(&out, length);
}

uint8_t *fcConfigGetConfigDirs(FcConfig *conf, size_t *length) {
    FcStrList *res = FcConfigGetConfigDirs(conf);
    if (res == NULL) return NULL;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {
        FcStrListDone(res);
        return NULL;
    }
    if (!encodeStrList(&out, res)) {
        cmp_bytes_free(&out);
        FcStrListDone(res);
        return NULL;
    }
    FcStrListDone(res);
    return cmp_bytes_take(&out, length);
}

uint8_t *fcConfigGetFontDirs(FcConfig *conf, size_t *length) {
    FcStrList *res = FcConfigGetFontDirs(conf);
    if (res == NULL) return NULL;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {
        FcStrListDone(res);
        return NULL;
    }
    if (!encodeStrList(&out, res)) {
        cmp_bytes_free(&out);
        FcStrListDone(res);
        return NULL;
    }
    FcStrListDone(res);
    return cmp_bytes_take(&out, length);
}

uint8_t *fcConfigGetConfigFiles(FcConfig *conf, size_t *length) {
    FcStrList *res = FcConfigGetConfigFiles(conf);
    if (res == NULL) return NULL;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {
        FcStrListDone(res);
        return NULL;
    }
    if (!encodeStrList(&out, res)) {
        cmp_bytes_free(&out);
        FcStrListDone(res);
        return NULL;
    }
    FcStrListDone(res);
    return cmp_bytes_take(&out, length);
}

uint8_t *fcConfigGetCacheDirs(FcConfig *conf, size_t *length) {
    FcStrList *res = FcConfigGetCacheDirs(conf);
    if (res == NULL) return NULL;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {
        FcStrListDone(res);
        return NULL;
    }
    if (!encodeStrList(&out, res)) {
        cmp_bytes_free(&out);
        FcStrListDone(res);
        return NULL;
    }
    FcStrListDone(res);
    return cmp_bytes_take(&out, length);
}

uint8_t *fcConfigGetFonts(FcConfig *conf, bool system, size_t *length) {
    FcFontSet *res = FcConfigGetFonts(conf, system ? FcSetSystem : FcSetApplication);
    if (res == NULL) return NULL;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) {
        FcFontSetDestroy(res);
        return NULL;
    }
    if (!encodeFontSet(&out, res)) {
        cmp_bytes_free(&out);
        FcFontSetDestroy(res);
        return NULL;
    }
    FcFontSetDestroy(res);
    return cmp_bytes_take(&out, length);
}

uint8_t *fcConfigSubstituteWithPat(FcConfig *conf, uint8_t *data, size_t in_length, bool isFont, size_t *length) {
    cmp_ctx_t in;
    if (!cmp_bytes_init(&in, data, in_length)) return NULL;

    uint32_t size = 0;
    if (!cmp_read_array(&in, &size) || size < 1 || size > 2) return NULL;
    FcPattern *p = decodePattern(&in);
    if (p == NULL) {cmp_bytes_take(&in, NULL); return NULL;}
    FcPattern *p_pat = NULL;
    if (size == 2) {
        p_pat = decodePattern(&in);
        if (p_pat == NULL) {cmp_bytes_take(&in, NULL); goto fail;}
    }
    cmp_bytes_take(&in, NULL);

    if (!FcConfigSubstituteWithPat(conf, p, p_pat, isFont ? FcMatchFont : FcMatchPattern)) goto fail;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) goto fail;
    if (!encodePattern(&out, p)) goto fail;
    FcPatternDestroy(p);
    if (p_pat != NULL) FcPatternDestroy(p_pat);
    return cmp_bytes_take(&out, length);

fail:
    FcPatternDestroy(p);
    if (p_pat != NULL) FcPatternDestroy(p_pat);
    return NULL;
}

uint8_t *fcFontMatch(FcConfig *conf, uint8_t *data, size_t in_length, size_t *length) {
    cmp_ctx_t in;
    if (!cmp_bytes_init(&in, data, in_length)) return NULL;
    FcPattern *p = decodePattern(&in);
    cmp_bytes_take(&in, NULL);
    if (p == NULL) return NULL;

    if (!FcConfigSubstitute(conf, p, FcMatchPattern)) goto fail;
    FcDefaultSubstitute(p);

    FcResult err;
    FcPattern *res = FcFontMatch(conf, p, &err);
    if (res == NULL) goto fail;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) goto fail;
    if (err != FcResultMatch) {
        if (!encodeResult(&out, err)) goto fail2;
    } else {
        if (!encodePattern(&out, res)) goto fail2;
    }
    FcPatternDestroy(p);
    return cmp_bytes_take(&out, length);;

fail2:
    cmp_bytes_free(&out);
fail:
    FcPatternDestroy(p);
    return NULL;
}

uint8_t *fcFontSort(FcConfig *conf, uint8_t *data, size_t in_length, bool trim, size_t *length) {
    cmp_ctx_t in;
    if (!cmp_bytes_init(&in, data, in_length)) return NULL;
    FcPattern *p = decodePattern(&in);
    cmp_bytes_take(&in, NULL);
    if (p == NULL) return NULL;

    if (!FcConfigSubstitute(conf, p, FcMatchPattern)) goto fail;
    FcDefaultSubstitute(p);

    FcResult err;
    FcCharSet *csp;
    FcFontSet *res = FcFontSort(conf, p, trim, &csp, &err);
    if (res == NULL) goto fail2;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) goto fail2;
    if (err != FcResultMatch) {
        if (!encodeResult(&out, err)) goto fail3;
    } else if (csp != NULL) {
        if (!cmp_write_array(&out, 2)) goto fail3;
        if (!encodeFontSet(&out, res)) goto fail3;
        if (!encodeCharSet(&out, csp)) goto fail3;
    } else {
        if (!cmp_write_array(&out, 1)) goto fail3;
        if (!encodeFontSet(&out, res)) goto fail3;
    }
    FcPatternDestroy(p);
    if (csp != NULL) FcCharSetDestroy(csp);
    return cmp_bytes_take(&out, length);

fail3:
    cmp_bytes_free(&out);
fail2:
    if (csp != NULL) FcCharSetDestroy(csp);
fail:
    FcPatternDestroy(p);
    return NULL;
}

uint8_t *fcFontRenderPrepare(FcConfig *conf, uint8_t *data, size_t in_length, size_t *length) {
    cmp_ctx_t in;
    if (!cmp_bytes_init(&in, data, in_length)) return NULL;
    uint32_t size = 0;
    if (!cmp_read_array(&in, &size) || size != 2) return NULL;
    FcPattern *pat = decodePattern(&in);
    if (pat == NULL) {cmp_bytes_take(&in, NULL); return NULL;}
    FcPattern *font = decodePattern(&in);
    cmp_bytes_take(&in, NULL);
    if (font == NULL) {FcPatternDestroy(pat); return NULL; }

    FcPattern *res = FcFontRenderPrepare(conf, pat, font);
    if (res == NULL) goto fail0;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) goto fail;
    if (!encodePattern(&out, res)) {cmp_bytes_free(&out); goto fail;}
    FcPatternDestroy(pat);
    FcPatternDestroy(font);
    FcPatternDestroy(res);
    return cmp_bytes_take(&out, length);

fail:
    FcPatternDestroy(res);
fail0:
    FcPatternDestroy(pat);
    FcPatternDestroy(font);
    return NULL;
}

uint8_t *fcFontList(FcConfig *conf, uint8_t *data, size_t in_length, size_t *length) {
    cmp_ctx_t in;
    if (!cmp_bytes_init(&in, data, in_length)) return NULL;
    uint32_t size = 0;
    if (!cmp_read_array(&in, &size) || size != 2) {
        cmp_bytes_take(&in, NULL);
        return NULL;
    }
    FcPattern *pat = decodePattern(&in);
    if (pat == NULL) {cmp_bytes_take(&in, NULL); return NULL;}
    FcObjectSet *os = decodeObjectSet(&in);
    cmp_bytes_take(&in, NULL);
    if (os == NULL) {FcPatternDestroy(pat); return NULL;}

    FcFontSet *res = FcFontList(conf, pat, os);
    if (res == NULL) goto fail0;

    cmp_ctx_t out;
    if (!cmp_bytes_alloc(&out, 1024)) goto fail;
    if (!encodeFontSet(&out, res)) { cmp_bytes_free(&out); goto fail;}
    FcFontSetDestroy(res);
    return cmp_bytes_take(&out, length);

fail:
    FcFontSetDestroy(res);
fail0:
    FcPatternDestroy(pat);
    FcObjectSetDestroy(os);
    return NULL;
}

/*int fcConfigAcceptFont(FcConfig *conf, uint8_t *data, size_t length) {
    cmp_ctx_t in;
    if (!cmp_bytes_init(&in, data, length)) return -1;
    FcPattern *pat = decodePattern(&in);
    if (pat == NULL) return -1;

    FcBool ret = FcConfigAcceptFont(conf, pat);
    FcPatternDestroy(pat);
    return ret ? 1 : 0;
}*/

R cbits/freetype-wrap.c => cbits/fontconfig-wrap.c~ +30 -0
@@ 3,6 3,36 @@
#include <stdlib.h>
#include <fontconfig/fcfreetype.h>

int fcPatternEqualSubset(uint8_t *data, size_t length) {
    cmp_ctx_t in;
    if (!cmp_bytes_init(&in, data, length)) return -1;
    uint32_t size = 0;
    if (!cmp_read_array(&in, &size) || size != 3) {
        cmp_bytes_take(&in, NULL);
        return -1;
    }
    FcPattern *pa = decodePattern(&in);
    if (pa == NULL) {cmp_bytes_take(&in, NULL); return -1;}
    FcPattern *pb = decodePattern(&in);
    if (pb == NULL) {
        cmp_bytes_take(&in, NULL);
        FcPatternDestroy(pa);
        return -1;
    }
    FcObjectSet *os = decodeObjectSet(&in);
    cmp_bytes_take(&in, NULL);

    int ret = -1;
    if (os != NULL) ret = FcPatternEqualSubset(pa, pb, os) ? 1 : 0;

    FcPatternDestroy(pa);
    FcPatternDestroy(pb);
    FcObjectSetDestroy(os);
    return ret;
}



uint8_t *fcNameParse(char *name, size_t *length) {
    FcPattern *pat = FcNameParse(name);
    if (pat == NULL) return NULL;

A cbits/fontconfig-wrap.h => cbits/fontconfig-wrap.h +37 -0
@@ 0,0 1,37 @@
#include <fontconfig/fontconfig.h>
#include <fontconfig/fcfreetype.h>
#include <stdint.h>
#include <stdbool.h>

int fcPatternEqualSubset(uint8_t *data, size_t length);
uint8_t *fcDefaultSubstitute(uint8_t *data, size_t in_length, size_t *length);
uint8_t *fcNameParse(char *name, size_t *length);
char *fcNameUnparse(uint8_t *data, size_t length);
char *fcNameFormat(uint8_t *data, size_t length, char *format);
uint8_t *fcFontSetList(FcConfig *config, uint8_t *sets, size_t sets_length,
        uint8_t *pat, size_t pat_length, uint8_t *objects, size_t objs_length, size_t *length);
uint8_t *fcFontSetMatch(FcConfig *config, uint8_t *sets, size_t sets_length,
        uint8_t *pat, size_t pat_length, size_t *length);
uint8_t *fcFontSetSort(FcConfig *config, uint8_t *sets, size_t sets_length,
        uint8_t *pat, size_t pat_length, bool trim, size_t *length);
unsigned int fcFreeTypeCharIndex(FT_Face *face, uint32_t ucs4);
uint8_t *fcFreeTypeCharSet(FT_Face *face, size_t *length);
uint8_t *fcFreeTypeCharSetAndSpacing(FT_Face *face, size_t *length);
uint8_t *fcFreeTypeQuery(char *file, int id, size_t *length);
uint8_t *fcFreeTypeQueryAll(char *file, size_t *length);
uint8_t *fcFreeTypeQueryFace(FT_Face *face, char *file, int id, size_t *length);
int fcLangSetHasLang(uint8_t *langset, size_t length, const char *lang);
uint8_t *fcGetDefaultLangs(size_t *length);
uint8_t *fcGetLangs(size_t *length);
char *fcLangNormalize(char *lang);
uint8_t *fcConfigGetConfigDirs(FcConfig *conf, size_t *length);
uint8_t *fcConfigGetFontDirs(FcConfig *conf, size_t *length);
uint8_t *fcConfigGetConfigFiles(FcConfig *conf, size_t *length);
uint8_t *fcConfigGetCacheDirs(FcConfig *conf, size_t *length);
uint8_t *fcConfigGetFonts(FcConfig *conf, bool system, size_t *length);
uint8_t *fcConfigSubstituteWithPat(FcConfig *conf, uint8_t *data, size_t in_length, bool isFont, size_t *length);
uint8_t *fcFontMatch(FcConfig *conf, uint8_t *data, size_t in_length, size_t *length);
uint8_t *fcFontSort(FcConfig *conf, uint8_t *data, size_t in_length, bool trim, size_t *length);
uint8_t *fcFontRenderPrepare(FcConfig *conf, uint8_t *data, size_t in_length, size_t *length);
uint8_t *fcFontList(FcConfig *conf, uint8_t *data, size_t in_length, size_t *length);


A cbits/fontconfig-wrap.h~ => cbits/fontconfig-wrap.h~ +37 -0
@@ 0,0 1,37 @@
#include <fontconfig/fontconfig.h>
#include <fontconfig/fcfreetype.h>
#include <stdint.h>
#include <stdbool.h>

int fcPatternEqualSubset(uint8_t *data, size_t length);
uint8_t *fcDefaultSubstitute(uint8_t *data, size_t in_length, size_t *)
uint8_t *fcNameParse(char *name, size_t *length);
char *fcNameUnparse(uint8_t *data, size_t length);
char *fcNameFormat(uint8_t *data, size_t length, char *format);
uint8_t *fcFontSetList(FcConfig *config, uint8_t *sets, size_t sets_length,
        uint8_t *pat, size_t pat_length, uint8_t *objects, size_t objs_length, size_t *length);
uint8_t *fcFontSetMatch(FcConfig *config, uint8_t *sets, size_t sets_length,
        uint8_t *pat, size_t pat_length, size_t *length);
uint8_t *fcFontSetSort(FcConfig *config, uint8_t *sets, size_t sets_length,
        uint8_t *pat, size_t pat_length, bool trim, size_t *length);
unsigned int fcFreeTypeCharIndex(FT_Face *face, uint32_t ucs4);
uint8_t *fcFreeTypeCharSet(FT_Face *face, size_t *length);
uint8_t *fcFreeTypeCharSetAndSpacing(FT_Face *face, size_t *length);
uint8_t *fcFreeTypeQuery(char *file, int id, size_t *length);
uint8_t *fcFreeTypeQueryAll(char *file, size_t *length);
uint8_t *fcFreeTypeQueryFace(FT_Face *face, char *file, int id, size_t *length);
int fcLangSetHasLang(uint8_t *langset, size_t length, const char *lang);
uint8_t *fcGetDefaultLangs(size_t *length);
uint8_t *fcGetLangs(size_t *length);
char *fcLangNormalize(char *lang);
uint8_t *fcConfigGetConfigDirs(FcConfig *conf, size_t *length);
uint8_t *fcConfigGetFontDirs(FcConfig *conf, size_t *length);
uint8_t *fcConfigGetConfigFiles(FcConfig *conf, size_t *length);
uint8_t *fcConfigGetCacheDirs(FcConfig *conf, size_t *length);
uint8_t *fcConfigGetFonts(FcConfig *conf, bool system, size_t *length);
uint8_t *fcConfigSubstituteWithPat(FcConfig *conf, uint8_t *data, size_t in_length, bool isFont, size_t *length);
uint8_t *fcFontMatch(FcConfig *conf, uint8_t *data, size_t in_length, size_t *length);
uint8_t *fcFontSort(FcConfig *conf, uint8_t *data, size_t in_length, bool trim, size_t *length);
uint8_t *fcFontRenderPrepare(FcConfig *conf, uint8_t *data, size_t in_length, size_t *length);
uint8_t *fcFontList(FcConfig *conf, uint8_t *data, size_t in_length, size_t *length);


M cbits/transcode.c => cbits/transcode.c +2 -2
@@ 380,7 380,7 @@ FcFontSet **decodeFontSets(cmp_ctx_t *bytes, size_t *nsets) {

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

    FcFontSet **ret = calloc(sizeof(FcFontSet *), size);
    uint32_t i;


@@ 467,7 467,7 @@ bool cmp_bytes_init(cmp_ctx_t *ctx, uint8_t *buf, size_t length) {
}

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

M fontconfig-pure.cabal => fontconfig-pure.cabal +6 -3
@@ 66,9 66,12 @@ library
    exposed-modules:  Graphics.Text.Font.Choose.CharSet, Graphics.Text.Font.Choose.LangSet,
            Graphics.Text.Font.Choose.ObjectSet, Graphics.Text.Font.Choose.Range,
            Graphics.Text.Font.Choose.StrSet, Graphics.Text.Font.Choose.Value,
            Graphics.Text.Font.Choose.Pattern, Graphics.Text.Font.Choose.FontSet
            Graphics.Text.Font.Choose.Pattern, Graphics.Text.Font.Choose.FontSet,
            Graphics.Text.Font.Choose.Config, Graphics.Text.Font.Choose.Result,
            Graphics.Text.Font.Choose.Internal.FFI

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

    pkgconfig-depends: fontconfig



@@ 82,7 85,7 @@ library
    build-depends:    base >=4.12 && <5, containers >=0.1 && <1, css-syntax,
            freetype2 >=0.2 && <0.3, hashable >=1.3 && <2, linear >=1.0.1 && <2,
            scientific, stylist-traits >=0.1.1 && <1, text, msgpack >= 1.0 && <2,
            vector >= 0.13 && <1
            vector >= 0.13 && <1, bytestring

    -- Directories containing source files.
    hs-source-dirs:   lib

A lib/Graphics/Text/Font/Choose/Config.hs => lib/Graphics/Text/Font/Choose/Config.hs +35 -0
@@ 0,0 1,35 @@
{-# LANGUAGE CApiFFI #-}
module Graphics.Text.Font.Choose.Config where

import Foreign.Ptr (Ptr, FunPtr)
import Foreign.ForeignPtr (ForeignPtr, newForeignPtr)

import Graphics.Text.Font.Choose.Result (throwBool, throwNull)

data Config'
type Config = ForeignPtr Config'



initLoadConfig :: IO Config
initLoadConfig = newForeignPtr fcConfigDestroy =<< throwNull =<< fcInitLoadConfig -- FIXME: What's proper memory-management here?

initLoadConfigAndFonts :: IO Config
initLoadConfigAndFonts = newForeignPtr fcConfigDestroy =<< throwNull =<< fcInitLoadConfigAndFonts -- FIXME: What's proper memory-management here?

init :: IO ()
init = throwBool =<< fcInit
foreign import capi "fontconfig/fontconfig.h FcFini" fini :: IO ()
foreign import capi "fontconfig/fontconfig.h FcGetVersion" version :: Int
reinit :: IO ()
reinit = throwBool =<< fcInitReinitialize
bringUptoDate :: IO ()
bringUptoDate = throwBool =<< fcInitBringUptoDate

foreign import capi "fontconfig/fontconfig.h FcInitLoadConfig" fcInitLoadConfig :: IO (Ptr Config')
foreign import capi "fontconfig/fontconfig.h FcInitLoadConfigAndFonts" fcInitLoadConfigAndFonts :: IO (Ptr Config')
foreign import capi "fontconfig/fontconfig.h FcInit" fcInit :: IO Bool
foreign import capi "fontconfig/fontconfig.h &FcConfigDestroy" fcConfigDestroy :: FunPtr (Ptr Config' -> IO ())

foreign import capi "fontconfig/fontconfig.h FcInitReinitialize" fcInitReinitialize :: IO Bool
foreign import capi "fontconfig/fontconfig.h FcInitBringUptoDate" fcInitBringUptoDate :: IO Bool

A lib/Graphics/Text/Font/Choose/Internal/FFI.hs => lib/Graphics/Text/Font/Choose/Internal/FFI.hs +36 -0
@@ 0,0 1,36 @@
module Graphics.Text.Font.Choose.Internal.FFI where

import Data.MessagePack (MessagePack, pack, unpack)
import Foreign.C.String (CString, withCString)
import Foreign.Ptr (Ptr)
import Foreign.Storable (Storable(..))
import Foreign.Marshal.Alloc (alloca)
import Data.Tuple (swap)
import Graphics.Text.Font.Choose.Result (throwNull)
import Data.Maybe (fromJust)

import Data.ByteString.Unsafe (unsafeUseAsCStringLen, unsafePackMallocCStringLen)
import Data.ByteString.Lazy (toStrict, fromStrict)
import System.IO.Unsafe (unsafePerformIO)

withMessage :: MessagePack a => (CString -> Int -> b) -> a -> b
withMessage inner arg = unsafePerformIO $
    unsafeUseAsCStringLen (toStrict $ pack arg) (return . uncurry inner)

fromMessage :: MessagePack a => (Ptr Int -> CString) -> Maybe a
fromMessage inner = unpack $ fromStrict $ unsafePerformIO $ do
    unsafePackMallocCStringLen . swap =<< withPtr (throwNull . inner)

fromMessage0 :: MessagePack a => (Ptr Int -> CString) -> a
fromMessage0 = fromJust . fromMessage

withCString' :: (CString -> a) -> String -> a
withCString' inner = unsafePerformIO . flip withCString (return . inner)

-- I don't want to pull in all of inline-c for this util!
withPtr :: (Storable a) => (Ptr a -> IO b) -> IO (a, b)
withPtr f = do
  alloca $ \ptr -> do
    x <- f ptr
    y <- peek ptr
    return (y, x)

M lib/Graphics/Text/Font/Choose/Pattern.hs => lib/Graphics/Text/Font/Choose/Pattern.hs +32 -1
@@ 1,4 1,4 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveGeneric, CApiFFI #-}
module Graphics.Text.Font.Choose.Pattern where

import Data.Map as M


@@ 6,7 6,15 @@ import Data.MessagePack (MessagePack(..), Object(..))
import Data.Hashable (Hashable(..))
import GHC.Generics (Generic)

import Foreign.C.String (CString, peekCString)
import Foreign.Ptr (Ptr)
import Control.Exception (throw)
import Graphics.Text.Font.Choose.Internal.FFI (withMessage, fromMessage0, withCString')
import System.IO.Unsafe (unsafePerformIO)

import Graphics.Text.Font.Choose.Value
import Graphics.Text.Font.Choose.ObjectSet
import Graphics.Text.Font.Choose.Result

type Pattern = Map String [(Binding, Value)]
data Pattern' = Pattern' { unPattern :: Pattern }


@@ 23,6 31,29 @@ instance MessagePack Binding where
    toObject Weak = ObjectBool False
    toObject Same = ObjectNil

equalSubset :: Pattern -> Pattern -> ObjectSet -> Bool
equalSubset a b os = case withMessage fcPatternEqualSubset [toObject a, toObject b, toObject os] of
    0 -> False
    1 -> True
    _ -> throw ErrOOM

foreign import capi "fontconfig-wrap.h" fcPatternEqualSubset :: CString -> Int -> Int

defaultSubstitute :: Pattern -> Pattern
defaultSubstitute = fromMessage0 . withMessage fcDefaultSubstitute

foreign import capi "fontconfig-wrap.h" fcDefaultSubstitute :: CString -> Int -> Ptr Int -> CString

nameParse :: String -> Pattern
nameParse = fromMessage0 . withCString' fcNameParse

foreign import capi "fontconfig-wrap.h" fcNameParse :: CString -> Ptr Int -> CString

nameUnparse :: Pattern -> String
nameUnparse = unsafePerformIO . peekCString . withMessage fcNameUnparse

foreign import capi "fontconfig-wrap.h" fcNameUnparse :: CString -> Int -> CString

------
--- CSS
------

A lib/Graphics/Text/Font/Choose/Result.hs => lib/Graphics/Text/Font/Choose/Result.hs +20 -0
@@ 0,0 1,20 @@
module Graphics.Text.Font.Choose.Result (FcException(..), throwBool, throwNull, throwString) where

import Foreign.Ptr (Ptr, nullPtr)
import Text.Read (readMaybe)
import Control.Exception (Exception, throwIO)

data FcException = ErrType | ErrNoId | ErrOOM | ErrOther deriving (Read, Show, Eq, Enum)
instance Exception FcException

throwBool :: Bool -> IO ()
throwBool False = throwIO ErrOOM
throwBool True = return ()

throwNull :: Ptr a -> IO (Ptr a)
throwNull a | a == nullPtr = throwIO ErrOOM
    | otherwise = return a

throwString :: String -> IO ()
throwString a | Just b <- readMaybe a :: Maybe FcException = throwIO b
    | otherwise = return ()