libui/common/attrlist.c

669 lines
18 KiB
C
Raw Normal View History

// 16 december 2016
#include "../ui.h"
#include "uipriv.h"
/*
An attribute list is a doubly linked list of attributes.
2016-12-22 12:05:36 -06:00
Attribute start positions are inclusive and attribute end positions are exclusive (or in other words, [start, end)).
The list is kept sorted in increasing order by start position. Whether or not the sort is stable is undefined, so no temporal information should be expected to stay.
Overlapping attributes are not allowed; if an attribute is added that conflicts with an existing one, the existing one is removed.
In addition, the linked list tries to reduce fragmentation: if an attribute is added that just expands another, then there will only be one entry in alist, not two. (TODO does it really?)
The linked list is not a ring; alist->fist->prev == NULL and alist->last->next == NULL.
*/
struct attr {
uiAttributeSpec spec;
size_t start;
size_t end;
struct attr *prev;
struct attr *next;
};
struct attrlist {
struct attr *first;
struct attr *last;
};
2016-12-17 11:19:33 -06:00
// if before is NULL, add to the end of the list
static void attrInsertBefore(struct attrlist *alist, struct attr *a, struct attr *before)
2016-12-17 11:19:33 -06:00
{
// if the list is empty, this is the first item
if (alist->first == NULL) {
alist->first = a;
alist->last = a;
return;
}
// add to the end
if (before == NULL) {
struct attr *oldlast;
oldlast = alist->last;
alist->last = a;
a->prev = oldlast;
oldlast->next = a;
return;
}
// add to the beginning
if (before == alist->first) {
struct attr *oldfirst;
oldfirst = alist->first;
alist->first = a;
oldfirst->prev = a;
a->next = oldfirst;
return;
}
// add to the middle
a->prev = before->prev;
a->next = before;
before->prev = a;
a->prev->next = a;
}
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;
}
2016-12-19 10:02:33 -06:00
// returns the old a->next, for forward iteration
static struct attr *attrUnlink(struct attrlist *alist, struct attr *a)
{
struct attr *p, *n;
p = a->prev;
n = a->next;
a->prev = NULL;
a->next = NULL;
// only item in list?
if (p == NULL && n == NULL) {
alist->first = NULL;
alist->last = NULL;
2016-12-19 10:02:33 -06:00
return NULL;
}
// start of list?
if (p == NULL) {
n->prev = NULL;
alist->first = n;
2016-12-19 10:02:33 -06:00
return n;
}
// end of list?
if (n == NULL) {
p->next = NULL;
alist->last = p;
2016-12-19 10:02:33 -06:00
return NULL;
}
// middle of list
p->next = n;
n->prev = p;
2016-12-19 10:02:33 -06:00
return n;
}
2016-12-19 10:02:33 -06:00
// returns the old a->next, for forward iteration
static struct attr *attrDelete(struct attrlist *alist, struct attr *a)
{
2016-12-19 10:02:33 -06:00
struct attr *next;
next = attrUnlink(alist, a);
uiFree(a);
2016-12-19 10:02:33 -06:00
return next;
}
// attrDropRange() removes attributes without deleting characters.
//
2016-12-19 10:02:33 -06:00
// If the attribute needs no change, then nothing is done.
//
2016-12-19 10:02:33 -06:00
// If the attribute needs to be deleted, it is deleted.
//
2016-12-19 10:02:33 -06:00
// If the attribute only needs to be resized at the end, it is adjusted.
//
2016-12-19 10:02:33 -06:00
// If the attribute only needs to be resized at the start, it is adjusted, unlinked, and returned in tail.
//
2016-12-19 10:02:33 -06:00
// Otherwise, the attribute needs to be split. The existing attribute is adjusted to make the left half and a new attribute with the right half. This attribute is kept unlinked and returned in tail.
//
2016-12-19 10:02:33 -06:00
// In all cases, the return value is the next attribute to look at in a forward sequential loop.
static struct attr *attrDropRange(struct attrlist *alist, struct attr *a, size_t start, size_t end, struct attr **tail)
{
struct attr *b;
// always pre-initialize tail to NULL
*tail = NULL;
2016-12-19 10:02:33 -06:00
if (!attrRangeIntersect(a, &start, &end))
// out of range; nothing to do
2016-12-19 10:02:33 -06:00
return a->next;
// just outright delete the attribute?
// the inequalities handle attributes entirely inside the range
// if both are equal, the attribute's range is equal to the range
if (a->start >= start && a->end <= end)
2016-12-19 10:02:33 -06:00
return attrDelete(alist, a);
// only chop off the start or end?
if (a->start == start) { // chop off the start
// we are dropping the left half, so set a->start and unlink
a->start = end;
*tail = a;
return attrUnlink(alist, a);
}
if (a->end == end) { // chop off the end
// we are dropping the right half, so just set a->end
a->end = start;
return a->next;
}
// we'll need to split the attribute into two
b = uiNew(struct attr);
b->spec = a->spec;
b->start = end;
b->end = a->end;
*tail = b;
a->end = start;
2016-12-19 10:02:33 -06:00
return a->next;
}
static void attrGrow(struct attrlist *alist, struct attr *a, size_t start, size_t end)
{
struct attr *before;
// adjusting the end is simple: if it ends before our new end, just set the new end
if (a->end < end)
a->end = end;
// adjusting the start is harder
// if the start is before our new start, we are done
// otherwise, we have to move the start back AND reposition the attribute to keep the sorted order
if (a->start <= start)
return;
a->start = start;
2016-12-19 10:02:33 -06:00
attrUnlink(alist, a);
for (before = alist->first; before != NULL; before = before->next)
if (before->start > a->start)
break;
attrInsertBefore(alist, a, before);
}
// returns the right side of the split, which is unlinked, or NULL if no split was done
static struct attr *attrSplitAt(struct attrlist *alist, struct attr *a, size_t at)
{
struct attr *b;
// no splittng needed?
// note the equality: we don't need to split at start or end
// in the end case, the last split point is at - 1; at itself is outside the range, and at - 1 results in the right hand side having length 1
if (at <= a->start)
return NULL;
if (at >= a->end)
return NULL;
b = uiNew(struct attr);
b->spec = a->spec;
b->start = at;
b->end = a->end;
a->end = at;
return b;
}
// attrDeleteRange() removes attributes while deleting characters.
//
// If the attribute does not include the deleted range, then nothing is done (though the start and end are adjusted as necessary).
//
// If the attribute needs to be deleted, it is deleted.
//
// Otherwise, the attribute only needs the start or end deleted, and it is adjusted.
//
// In all cases, the return value is the next attribute to look at in a forward sequential loop.
// TODO rewrite this comment
static struct attr *attrDeleteRange(struct attrlist *alist, struct attr *a, size_t start, size_t end)
{
size_t ostart, oend;
size_t count;
ostart = start;
oend = end;
count = oend - ostart;
if (!attrRangeIntersect(a, &start, &end)) {
// out of range
// adjust if necessary
if (a->start >= ostart)
a->start -= count;
if (a->end >= oend)
a->end -= count;
return a->next;
}
// just outright delete the attribute?
// the inequalities handle attributes entirely inside the range
// if both are equal, the attribute's range is equal to the range
if (a->start >= start && a->end <= end)
return attrDelete(alist, a);
// only chop off the start or end?
if (a->start == start) { // chop off the start
// if we weren't adjusting positions this would just be setting a->start to end
// but since this is deleting from the start, we need to adjust both by count
a->start = end - count;
a->end -= count;
return a->next;
}
if (a->end == end) { // chop off the end
// a->start is already good
a->end = start;
return a->next;
}
// in this case, the deleted range is inside the attribute
// we can clear it by just removing count from a->end
a->end -= count;
return a->next;
}
static int boolsEqual(struct attr *attr, uiAttributeSpec *spec)
{
if (attr->spec.Value == 0 && spec->Value == 0)
return 1;
return attr->spec.Value != 0 && spec->Value != 0;
}
2017-02-15 08:44:57 -06:00
// BCP 47 is ASCII-only
static int asciiStringsEqualCaseFold(const char *a, const char *b)
{
char c, d;
for (;;) {
if (*a == *b) {
if (*a == '\0')
return 1;
a++;
b++;
continue;
}
c = *a;
if (c >= 'A' && c <= 'Z')
c += 'a' - 'A';
d = *b;
if (d >= 'A' && d <= 'Z')
d += 'a' - 'A';
if (c != d)
return 0;
a++;
b++;
}
}
static int specsIdentical(struct attr *attr, uiAttributeSpec *spec)
{
if (attr->spec.Type != spec->Type)
return 0;
switch (attr->spec.Type) {
case uiAttributeFamily:
// TODO should we start copying these strings?
2017-02-15 08:44:57 -06:00
// TODO should this be case-insensitive?
return strcmp((char *) (attr->spec.Value), (char *) (spec->Value)) == 0;
case uiAttributeSize:
// TODO use a closest match?
return attr->spec.Double == spec->Double;
case uiAttributeUnderlineColor:
if (attr->spec.Value != spec->Value)
return 0;
if (attr->spec.Value != uiDrawUnderlineColorCustom)
return 1;
// otherwise fall through
case uiAttributeColor:
case uiAttributeBackground:
// TODO use a closest match?
return attr->spec.R == spec->R &&
attr->spec.G == spec->G &&
attr->spec.B == spec->B &&
attr->spec.A == spec->A;
}
2017-05-17 18:21:27 -05:00
// handles the rest, including pointer comparison for uiAttributeFeatures
// TODO rename it to uiAttributeOpenTypeFeatures?
return attr->spec.Value == spec->Value;
}
void attrlistInsertAttribute(struct attrlist *alist, uiAttributeSpec *spec, size_t start, size_t end)
{
struct attr *a;
struct attr *before;
struct attr *tail = NULL;
int split = 0;
// first, figure out where in the list this should go
// in addition, if this attribute overrides one that already exists, split that one apart so this one can take over
before = alist->first;
while (before != NULL) {
size_t lstart, lend;
// once we get to the first point after start, we know where to insert
if (before->start > start)
break;
// if we have already split a prior instance of this attribute, don't bother doing it again
if (split)
goto next;
// should we split this?
if (before->spec.Type != spec->Type)
goto next;
lstart = start;
lend = end;
2016-12-22 12:05:36 -06:00
if (!attrRangeIntersect(before, &lstart, &lend))
goto next;
// okay so this might conflict; if the val is the same as the one we want, we need to expand the existing attribute, not fragment anything
// TODO will this reduce fragmentation if we first add from 0 to 2 and then from 2 to 4? or do we have to do that separately?
if (specsIdentical(before, spec)) {
attrGrow(alist, before, start, end);
return;
}
// okay the values are different; we need to split apart
before = attrDropRange(alist, before, start, end, &tail);
split = 1;
continue;
next:
before = before->next;
}
// if we got here, we know we have to add the attribute before before
a = uiNew(struct attr);
a->spec = *spec;
a->start = start;
a->end = end;
attrInsertBefore(alist, a, before);
// and finally, if we split, insert the remainder
if (tail == NULL)
return;
// note we start at before; it won't be inserted before that by the sheer nature of how the code above works
for (; before != NULL; before = before->next)
if (before->start > tail->start)
break;
attrInsertBefore(alist, tail, before);
}
void attrlistInsertCharactersUnattributed(struct attrlist *alist, size_t start, size_t count)
{
struct attr *a;
struct attr *tails = NULL;
// every attribute before the insertion point can either cross into the insertion point or not
// if it does, we need to split that attribute apart at the insertion point, keeping only the old attribute in place, adjusting the new tail, and preparing it for being re-added later
for (a = alist->first; a != NULL; a = a->next) {
struct attr *tail;
// stop once we get to the insertion point
if (a->start >= start)
break;
// only do something if overlapping
if (!attrHasPos(a, start))
continue;
tail = attrSplitAt(alist, a, start);
// adjust the new tail for the insertion
tail->start += count;
tail->end += count;
// and queue it for re-adding later
// we can safely use tails as if it was singly-linked since it's just a temporary list; we properly merge them back in below and they'll be doubly-linked again then
// TODO actually we could probably save some time by making then doubly-linked now and adding them in one fell swoop, but that would make things a bit more complicated...
tail->next = tails;
tails = tail;
}
// at this point in the attribute list, we are at or after the insertion point
// all the split-apart attributes will be at the insertion point
// therefore, we can just add them all back now, and the list will still be sorted correctly
while (tails != NULL) {
struct attr *next;
// make all the links NULL before insertion, just to be safe
next = tails->next;
tails->next = NULL;
attrInsertBefore(alist, tails, a);
tails = next;
}
// every remaining attribute will be either at or after the insertion point
// we just need to move them ahead
for (; a != NULL; a = a->next) {
a->start += count;
a->end += count;
}
}
// The attributes are those of character start - 1.
// If start == 0, the attributes are those of character 0.
/*
This is an obtuse function. Here's some diagrams to help.
Given the input string
abcdefghi (positions: 012345678 9)
and attribute set
red start 0 end 3
bold start 2 end 6
underline start 5 end 8
or visually:
012345678 9
rrr------
--bbbb---
-----uuu-
If we insert qwe to result in positions 0123456789AB C:
before 0, 1, 2 (grow the red part, move everything else down)
red -> start 0 (no change) end 3+3=6
bold -> start 2+3=5 end 6+3=9
underline -> start 5+3=8 end 8+3=B
before 3 (grow red and bold, move underline down)
red -> start 0 (no change) end 3+3=6
bold -> start 2 (no change) end 6+3=9
underline -> start 5+3=8 end 8+3=B
before 4, 5 (keep red, grow bold, move underline down)
red -> start 0 (no change) end 3 (no change)
bold -> start 2 (no change) end 6+3=9
underline -> start 5+3=8 end 8+3=B
before 6 (keep red, grow bold and underline)
red -> start 0 (no change) end 3 (no change)
bold -> start 2 (no change) end 6+3=9
underline -> start 5 (no change) end 8+3=B
before 7, 8 (keep red and bold, grow underline)
red -> start 0 (no change) end 3 (no change)
bold -> start 2 (no change) end 6 (no change)
underline -> start 5 (no change) end 8+3=B
before 9 (keep all three)
red -> start 0 (no change) end 3 (no change)
bold -> start 2 (no change) end 6 (no change)
underline -> start 5 (no change) end 8 (no change)
result:
0 1 2 3 4 5 6 7 8 9
red E E E e n n n n n n
bold s s S E E E e n n n
underline s s s s s S E E e n
N = none
E = end only
S = start and end
uppercase = in original range, lowercase = not
which results in our algorithm:
for each attribute
if start < insertion point
move start up
else if start == insertion point
if start != 0
move start up
if end <= insertion point
move end up
*/
// TODO does this ensure the list remains sorted?
void attrlistInsertCharactersExtendingAttributes(struct attrlist *alist, size_t start, size_t count)
{
struct attr *a;
for (a = alist->first; a != NULL; a = a->next) {
if (a->start < start)
a->start += count;
else if (a->start == start && start != 0)
a->start += count;
if (a->end <= start)
a->end += count;
}
}
2016-12-22 12:05:36 -06:00
// TODO replace at point with — replaces with first character's attributes
void attrlistRemoveAttribute(struct attrlist *alist, uiAttribute type, size_t start, size_t end)
{
2016-12-22 12:05:36 -06:00
struct attr *a;
struct attr *tails = NULL; // see attrlistInsertCharactersUnattributed() above
struct attr *tailsAt = NULL;
a = alist->first;
2016-12-22 12:05:36 -06:00
while (a != NULL) {
size_t lstart, lend;
struct attr *tail;
2016-12-22 12:05:36 -06:00
// this defines where to re-attach the tails
// (all the tails will have their start at end, so we can just insert them all before tailsAt)
if (a->start >= end) {
tailsAt = a;
// and at this point we're done, so
break;
}
if (a->spec.Type != type)
2016-12-22 12:05:36 -06:00
goto next;
lstart = start;
lend = end;
if (!attrRangeIntersect(a, &lstart, &lend))
goto next;
a = attrDropRange(alist, a, start, end, &tail);
if (tail != NULL) {
tail->next = tails;
tails = tail;
}
continue;
next:
a = a->next;
}
while (tails != NULL) {
struct attr *next;
// make all the links NULL before insertion, just to be safe
next = tails->next;
tails->next = NULL;
attrInsertBefore(alist, tails, a);
tails = next;
}
}
// TODO merge this with the above
void attrlistRemoveAttributes(struct attrlist *alist, size_t start, size_t end)
{
struct attr *a;
struct attr *tails = NULL; // see attrlistInsertCharactersUnattributed() above
struct attr *tailsAt = NULL;
a = alist->first;
while (a != NULL) {
size_t lstart, lend;
struct attr *tail;
// this defines where to re-attach the tails
// (all the tails will have their start at end, so we can just insert them all before tailsAt)
if (a->start >= end) {
tailsAt = a;
// and at this point we're done, so
break;
}
lstart = start;
lend = end;
if (!attrRangeIntersect(a, &lstart, &lend))
goto next;
a = attrDropRange(alist, a, start, end, &tail);
if (tail != NULL) {
tail->next = tails;
tails = tail;
}
continue;
next:
a = a->next;
}
while (tails != NULL) {
struct attr *next;
// make all the links NULL before insertion, just to be safe
next = tails->next;
tails->next = NULL;
attrInsertBefore(alist, tails, a);
tails = next;
}
}
void attrlistRemoveCharacters(struct attrlist *alist, size_t start, size_t end)
{
struct attr *a;
a = alist->first;
while (a != NULL)
a = attrDeleteRange(alist, a, start, end);
}
void attrlistForEach(struct attrlist *alist, uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data)
{
struct attr *a;
for (a = alist->first; a != NULL; a = a->next)
// TODO document this
// TODO should this be return 0 to break?
if ((*f)(s, &(a->spec), a->start, a->end, data))
break;
}
struct attrlist *attrlistNew(void)
{
return uiNew(struct attrlist);
}
void attrlistFree(struct attrlist *alist)
{
struct attr *a, *next;
a = alist->first;
while (a != NULL) {
next = a->next;
uiFree(a);
a = next;
}
uiFree(alist);
}