#include "property.h" #include #include #include #include "clay.h" #include "context.h" #include "clay-map.h" static struct clay_prop_val_s parse_prop_val_int(int val, const clay_prop_definition *def); static struct clay_prop_val_s parse_prop_val_double(double val, const clay_prop_definition *def); static struct clay_prop_val_s parse_prop_val_string_numeric(const char *val, const clay_prop_definition *def); static struct clay_prop_val_s parse_prop_val_string_notnumeric(const char *val, const clay_prop_definition *def); static struct clay_prop_val_s parse_prop_val_string(const char *val, const clay_prop_definition *def); static struct clay_prop_val_s parse_prop_val_color(clay_color val, const clay_prop_definition *def); static struct clay_prop_val_s parse_prop_val(struct clay_prop_val_in in, const clay_prop_definition *def); clay_prop clay_layout_get_property(clay layout, const char *prop_name) { const clay_prop_definition *prop_def = clay_ctx_get_property_definition(layout->ctx, layout->class, prop_name); clay_prop prop; if (!prop_def->inherits) { if (!clay_map_get(layout->properties, prop_name, &(prop.values))) { prop.values = NULL; } return prop; } bool found = false; clay cur = layout; prop.inheritance_level = 0; while (cur != NULL) { if (prop_def->global || layout->class == cur->class) { if (clay_map_get(cur->properties, prop_name, &prop.values)) { found = true; break; } } cur = cur->parent; prop.inheritance_level += 1; } if (!found) { prop.values = NULL; prop.inheritance_level = 0; } return prop; } clay_prop_map clay_layout_get_properties(clay layout) { clay_prop_map props = clay_map_create(clay_prop_map); clay cur = layout; int inheritance_level = 0; while (cur != NULL) { CLAY_MAP_FOREACH(cur->properties, propname, propvalueptr) { const clay_prop_definition *propdef = clay_ctx_get_property_definition(cur->ctx, cur->class, propname); if (inheritance_level == 0 || (propdef->inherits && (propdef->global || cur->class == layout->class))) { if (!clay_map_has_key(props, propname)) { clay_prop prop = {.values = *propvalueptr, .inheritance_level = inheritance_level}; clay_map_set(props, propname, prop); } } } cur = cur->parent; inheritance_level += 1; } return props; } void clay_layout_set_property(clay layout, const char *prop_name, int num_values, ...) { clay_ctx ctx = layout->ctx; const clay_prop_definition *def = clay_ctx_get_property_definition(ctx, layout->class, prop_name); assert(def != NULL); assert(num_values <= def->max_values); clay_prop_vals vals = clay_list_create(clay_prop_vals, num_values); if (num_values > 0) { va_list args; va_start(args, num_values); for (int i = 0; i < num_values; ++i) { struct clay_prop_val_in inval = va_arg(args, struct clay_prop_val_in); struct clay_prop_val_s val = parse_prop_val(inval, def); clay_list_append(vals, val); } va_end(args); } clay_map_set(layout->properties, prop_name, vals); } static struct clay_prop_val_s parse_prop_val_int(int val, const clay_prop_definition *def) { assert(def->allow_int); clay_prop_val_unit unit; if (!(def->allow_units & CLAY_PROP_UNIT_NONE) && def->allow_units & CLAY_PROP_UNIT_PX) { unit = CLAY_PROP_UNIT_PX; } else { unit = CLAY_PROP_UNIT_NONE; } assert(def->allow_units & unit); return (struct clay_prop_val_s) { .type = CLAY_PROP_TYPE_NUMBER, .number = (struct clay_prop_val_number) { .type = CLAY_PROP_NUMBER_INT, .unit = unit, .value_int = val } }; } static struct clay_prop_val_s parse_prop_val_double(double val, const clay_prop_definition *def) { assert(def->allow_float); assert(def->allow_units & CLAY_PROP_UNIT_NONE); return (struct clay_prop_val_s) { .type = CLAY_PROP_TYPE_NUMBER, .number = (struct clay_prop_val_number) { .type = CLAY_PROP_NUMBER_FLOAT, .unit = CLAY_PROP_UNIT_NONE, .value_double = val } }; } #include static clay_prop_val parse_prop_val_string_numeric(const char *val, const clay_prop_definition *def) { // TODO: it is wasteful to call newlocale for every parse, cache globally // or find a locale-independent correct implementation of the strto* functions clay_prop_val result = { .type = CLAY_PROP_TYPE_NUMBER, .number = {} }; locale_t locale = newlocale(LC_ALL, "C", NULL); locale_t original = uselocale(locale); char * endptr; int64_t int_val = strtoll(val, &endptr, 10); ptrdiff_t int_len = endptr - val; double float_val = strtod(val, &endptr); ptrdiff_t float_len = endptr - val; const char * nextptr; if (float_len > int_len || (float_len == int_len && !def->allow_int)) { assert(def->allow_float); result.number.type = CLAY_PROP_NUMBER_FLOAT; result.number.value_double = float_val; nextptr = val + float_len; } else { assert(def->allow_int); result.number.type = CLAY_PROP_NUMBER_INT; result.number.value_int = int_val; nextptr = val + int_len; } // consume spaces after number while(isspace(*nextptr)) ++nextptr; if (strncmp(nextptr, "px", 2) == 0) { result.number.unit = CLAY_PROP_UNIT_PX; assert(def->allow_units & CLAY_PROP_UNIT_PX); nextptr += 2; } else if (strncmp(nextptr, "pt", 2) == 0) { result.number.unit = CLAY_PROP_UNIT_PT; assert(def->allow_units & CLAY_PROP_UNIT_PT); nextptr += 2; } else if(strncmp(nextptr, "cm", 2) == 0) { result.number.unit = CLAY_PROP_UNIT_CM; assert(def->allow_units & CLAY_PROP_UNIT_CM); nextptr += 2; } else if(strncmp(nextptr, "mm", 2) == 0) { result.number.unit = CLAY_PROP_UNIT_MM; assert(def->allow_units & CLAY_PROP_UNIT_MM); nextptr += 2; } else if(strncmp(nextptr, "%", 1) == 0) { result.number.unit = CLAY_PROP_UNIT_PERCENT; assert(def->allow_units & CLAY_PROP_UNIT_PERCENT); nextptr += 1; } else { if (def->allow_units & CLAY_PROP_UNIT_NONE) { result.number.unit = CLAY_PROP_UNIT_NONE; } else if (def->allow_units & CLAY_PROP_UNIT_PX) { result.number.unit = CLAY_PROP_UNIT_PX; } else { assert(false); } } // consume spaces after unit while(isspace(*nextptr)) ++nextptr; // ensure nothing except whitespace appeared after the number(+unit) assert(*nextptr == '\0'); uselocale(original); return result; } static struct clay_prop_val_s parse_prop_val_string_notnumeric(const char *val, const clay_prop_definition *def) { if (val[0] == '#') { size_t len = strlen(val) - 1; if (len == 3 || len == 4 || len == 6 || len == 8) { bool is_color = true; uint8_t parts[8] = {0}; for (size_t i = 0; i < len; ++i) { uint8_t ch = val[1 + i]; if (ch >= '0' && ch <= '9') { parts[i] = ch - '0'; } else if (ch >= 'a' && ch <= 'f') { parts[i] = ch - 'a' + 0xA; } else if (ch >= 'A' && ch <= 'F') { parts[i] = ch - 'A' + 0xA; } else { is_color = false; break; } } if (is_color) { clay_color color = {}; switch (len) { case 3: color.red = parts[0] << 4 | parts[0]; color.green = parts[1] << 4 | parts[1]; color.blue = parts[2] << 4 | parts[2]; color.alpha = 255; break; case 4: color.red = parts[0] << 4 | parts[0]; color.green = parts[1] << 4 | parts[1]; color.blue = parts[2] << 4 | parts[2]; color.alpha = parts[3] << 4 | parts[3]; break; case 6: color.red = parts[0] << 4 | parts[1]; color.green = parts[2] << 4 | parts[3]; color.blue = parts[4] << 4 | parts[5]; color.alpha = 255; break; case 8: color.red = parts[0] << 4 | parts[1]; color.green = parts[2] << 4 | parts[3]; color.blue = parts[4] << 4 | parts[5]; color.alpha = parts[6] << 4 | parts[7]; break; default: assert(false); } return (struct clay_prop_val_s) {.type=CLAY_PROP_TYPE_COLOR, .color=color}; } } } int keyword_val; if (clay_map_get(def->keywords, val, &keyword_val)) { return (struct clay_prop_val_s) {.type=CLAY_PROP_TYPE_KEYWORD, .keyword=keyword_val}; } assert(def->allow_string); return (struct clay_prop_val_s) {.type=CLAY_PROP_TYPE_STRING, .string=val}; } static struct clay_prop_val_s parse_prop_val_string(const char *val, const clay_prop_definition *def) { assert(val != NULL && val[0] != '\0'); // no null refs and no empty strings if (isdigit(val[0]) || (val[0] == '-' && isdigit(val[0]))) { return parse_prop_val_string_numeric(val, def); } else { return parse_prop_val_string_notnumeric(val, def); } } static struct clay_prop_val_s parse_prop_val_color(clay_color val, const clay_prop_definition *def) { assert(def->allow_color); return (struct clay_prop_val_s) {.type = CLAY_PROP_TYPE_COLOR, .color = val}; } static struct clay_prop_val_s parse_prop_val(struct clay_prop_val_in in, const clay_prop_definition *def) { switch (in.type) { case TYPE_INT: return parse_prop_val_int(in.value_int, def); case TYPE_DOUBLE: return parse_prop_val_double(in.value_double, def); case TYPE_STRING: return parse_prop_val_string(in.value_string, def); case TYPE_COLOR: return parse_prop_val_color(in.value_color, def); } assert(false); }