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