Started implementing the attribute handling code itself.

This commit is contained in:
Pietro Gagliardi 2016-12-05 18:32:51 -05:00
parent b45e5f4de2
commit cb8d75d431
1 changed files with 253 additions and 7 deletions

View File

@ -6,7 +6,9 @@ struct uiAttributedString {
char *s;
size_t len;
// TODO attributes
size_t nAttrs; // TODO this needs to be maintained; is it necessary?
struct attr *attrs;
struct attr *lastattr;
// indiscriminately keep a UTF-16 copy of the string on all platforms so we can hand this off to the grapheme calculator
// this ensures no one platform has a speed advantage (sorry GTK+)
@ -20,6 +22,237 @@ struct uiAttributedString {
struct graphemes *graphemes;
};
struct attr {
int type;
uintptr_t val;
size_t start;
size_t end;
struct attr *next;
};
enum {
// TODO put attr types here
nAttrTypes,
};
static int attrHasPos(struct attr *a, size_t pos)
{
if (pos < a->start)
return 0;
return pos < a->end;
}
// returns 1 if there was an intersection and 0 otherwise
static int attrRangeIntersect(struct attr *a, size_t *start, size_t *end)
{
// is the range outside a entirely?
if (*start >= a->end)
return 0;
if (*end < a->start)
return 0;
// okay, so there is an overlap
// compute the intersection
if (*start < a->start)
*start = a->start;
if (*end > a->end)
*end = a->end;
return 1;
}
// returns:
// - 0 if no change needed
// - 1 if the attribute was split
static int attrSplitAt(uiAttributedString *s, struct attr *a, size_t at)
{
struct attr *b;
// no splittng needed?
if (at == a->start)
return 0;
if ((at + 1) == a->end)
return 0;
b = uiNew(struct attr);
b->what = a->what;
b->val = a->val;
b->start = at;
b->end = a->end;
b->next = a->next;
a->end = at;
a->next = b;
if (a == s->lastattr)
s->lastattr = a->next;
return 1;
}
// returns:
// - 0 if the attribute needs to be deleted
// - 1 if the attribute was changed or no change needed
// - 2 if the attribute was split
static int attrDropRange(uiAttributedString *s, struct attr *a, size_t start, size_t end)
{
struct attr *b;
if (!attrRangeIntersect(a, &start, &end))
// out of range; nothing to do
return 1;
// just outright delete the attribute?
if (a->start == start && a->end == end)
return 0;
// only chop off the start or end?
if (a->start == start) { // chop off the end
a->end = end;
return 1;
}
if (a->end == end) { // chop off the start
a->start = start;
return 1;
}
// we'll need to split the attribute into two
b = uiNew(struct attr);
b->what = a->what;
b->val = a->val;
b->start = end;
b->end = a->end;
b->next = a->next;
a->end = start;
a->next = b;
if (a == s->lastattr)
s->lastattr = a->next;
return 2;
}
// returns the attribute to continue with
static struct attr *attrDelete(uiAttributedString *s, struct attr *a, struct attr *prev)
{
if (a == s->attrs) {
s->attrs = a->next;
uiFree(a);
return s->attrs;
}
if (a == s->lastattr)
s->lastattr = prev;
prev->next = a->next;
uiFree(a);
return prev->next;
}
static void attrAppend(uiAttributedString *s, int type, uintptr_t val, size_t start, size_t end)
{
struct attr *a;
a = uiNew(struct attr);
a->type = type;
a->val = val;
a->start = start;
a->end = end;
if (s->attrs == NULL) {
s->attrs = a;
s->lastattr = a;
return;
}
s->lastattr->next = a;
s->lastattr = a;
}
// alist should be struct attr *alist[nAttrTypes]
static void attrsGetFor(uiAttributedString *s, struct attr **alist, size_t at)
{
int i;
struct attr *a;
// we want the attributes for at
// these are the attributes of at - 1
// if at == 0. then htese are the attributes at 0
if (at != 0)
at--;
// make usre unset attributes are NULL
for (i = 0; i < nAttrTypes; i++)
alist[i] = NULL;
for (a = s->attrs; a != NULL; a = a->next) {
if (!attrHasPos(a, at))
continue;
alist[a->type] = a;
}
}
// TODO have a routine that prunes overridden attributes for a given character from the list? merge it with the above?
static void attrAdjustPostInsert(uiAttributedString *s, size_t start, size_t n, size_t oldlen)
{
struct attr *a;
for (a = s->attrs; a != NULL; a = a->next) {
size_t astart, aend;
int splitNeeded;
// do we need to adjust this, and where?
astart = start;
aend = oldlen;
if (!attrRangeIntersect(a, &astart, &aend))
continue;
// if only part of the attribute falls in the moved area, we need to split at the insertion point and adjust both resultant attributes
// otherwise, we only adjust the original attribute
// split *before* adjusting so that the split is correct
splitNeeded = attrSplit(s, a, astart);
if (a->start >= start)
a->start += n;
if (a->end >= end)
a->end += n;
if (splitNeeded == 1) {
a = a->next;
if (a->start >= start)
a->start += n;
if (a->end >= end)
a->end += n;
}
}
}
// TODO this is very very wrong
static void attrAdjustPostDelete(uiAttributedString *s, size_t start, size_t end)
{
struct attr *a, *prev;
a = s->attrs;
prev = NULL;
while (a != NULL) {
size_t astart, aend;
// do we need to adjust this, and where?
astart = start;
aend = end;
if (!attrRangeIntersect(a, &astart, &aend)) {
prev = a;
a = a->next;
continue;
}
switch (attrDropRange(s, a, astart, aend)) {
case 0: // delete
a = attrDelete(s, a, prev);
// keep prev unchanged
break;
case 2: // split
a = a->next;
// fall through
case 1: // existing only needed adjustment
prev = a;
a = a->next;
break;
}
}
}
static void resize(uiAttributedString *s, size_t u8, size_t u16)
{
s->len = u8;
@ -63,6 +296,14 @@ static void invalidateGraphemes(uiAttributedString *s)
void uiFreeAttributedString(uiAttributedString *s)
{
struct attr *a, *b;
a = s->attrs;
while (a != NULL) {
b = a->next;
uiFree(a);
a = b;
}
invalidateGraphemes(s);
uiFree(s->u16tou8);
uiFree(s->u8tou16);
@ -119,7 +360,7 @@ void uiAttributedStringInsertAtUnattributed(uiAttributedString *s, const char *s
uint32_t rune;
char buf[4];
uint16_t u16buf[2];
size_t n, n16;
size_t n8, n16;
size_t old, old16;
size_t oldlen, old16len;
size_t at16;
@ -136,14 +377,14 @@ void uiAttributedStringInsertAtUnattributed(uiAttributedString *s, const char *s
// first figure out how much we need to grow by
// this includes post-validated UTF-8
u8u16len(str, &n, &n16);
u8u16len(str, &n8, &n16);
// and resize
old = at;
old16 = at16;
oldlen = s->len;
old16len = s->u16len;
resize(s, s->len + n, s->u16len + n16);
resize(s, s->len + n8, s->u16len + n16);
// move existing characters out of the way
// note the use of memmove(): https://twitter.com/rob_pike/status/737797688217894912
@ -167,6 +408,8 @@ void uiAttributedStringInsertAtUnattributed(uiAttributedString *s, const char *s
// and copy
while (*str) {
size_t n;
str = utf8DecodeRune(str, 0, &rune);
n = utf8EncodeRune(rune, buf);
n16 = utf16EncodeRune(rune, buf16);
@ -198,12 +441,15 @@ void uiAttributedStringInsertAtUnattributed(uiAttributedString *s, const char *s
s->u8tou16[old] = old16;
s->u16tou8[old16] = old;
// and finally adjust the prior values in the conversion tables
// and adjust the prior values in the conversion tables
// use <= so the terminating 0 gets updated too
for (i = 0; i <= oldlen - at; i++)
s->u8tou16[at + n + i] += n16;
s->u8tou16[at + n8 + i] += n16;
for (i = 0; i <= old16len - at16; i++)
s->u16tou8[at16 + n16 + i] += n;
s->u16tou8[at16 + n16 + i] += n8;
// and finally do the attributes
attrAdjustPostInsert(s, at, n8, oldlen);
}
// TODO document that end is the first index that will be maintained