#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;
}*/