2016-12-16 22:31:04 -06:00
// 16 december 2016
2016-12-18 13:11:12 -06:00
/*
An attribute list is a doubly linked list of attributes .
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 .
*/
2016-12-16 22:31:04 -06:00
struct attr {
uiAttribute type ;
uintptr_t val ;
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
2016-12-18 13:06:37 -06:00
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 ;
}
2016-12-17 22:07:48 -06:00
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-18 10:49:54 -06:00
static void attrUnlink ( struct attrlist * alist , struct attr * a , struct attr * * next )
2016-12-16 22:31:04 -06:00
{
2016-12-18 10:49:54 -06:00
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 ;
* next = NULL ;
return ;
}
// start of list?
if ( p = = NULL ) {
n - > prev = NULL ;
alist - > first = n ;
* next = n ;
return ;
}
// end of list?
if ( n = = NULL ) {
p - > next = NULL ;
alist - > last = p ;
* next = NULL ;
return ;
}
// middle of list
p - > next = n ;
n - > prev = p ;
* next = n ;
}
2016-12-16 22:31:04 -06:00
2016-12-18 10:49:54 -06:00
static void attrDelete ( struct attrlist * alist , struct attr * a , struct attr * * next )
{
attrUnlink ( alist , a , next ) ;
uiFree ( a ) ;
}
2016-12-16 22:31:04 -06:00
2016-12-18 10:49:54 -06:00
// attrDropRange() removes attributes without deleting characters.
//
// If the attribute needs no change, 1 is returned.
//
// If the attribute needs to be deleted, it is deleted and 0 is returned.
//
// If the attribute only needs to be resized at the end, it is adjusted and 1 is returned.
//
// If the attribute only needs to be resized at the start, it is adjusted, unlinked, and returned in tail, and 1 is returned.
//
// 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. Then, 2 is returned.
//
// In all cases, next is set to the next attribute to look at in a forward sequential loop.
//
// TODO is the return value relevant?
static int attrDropRange ( struct attrlist * alist , struct attr * a , size_t start , size_t end , struct attr * * tail , struct attr * * next )
{
struct attr * b ;
2016-12-17 22:07:48 -06:00
2016-12-18 10:49:54 -06:00
// always pre-initialize tail to NULL
* tail = NULL ;
if ( ! attrRangeIntersect ( a , & start , & end ) ) {
// out of range; nothing to do
* next = a - > next ;
return 1 ;
2016-12-17 22:07:48 -06:00
}
2016-12-18 10:49:54 -06:00
// just outright delete the attribute?
if ( a - > start = = start & & a - > end = = end ) {
attrDelete ( alist , a , next ) ;
return 0 ;
}
// only chop off the start or end?
if ( a - > start = = start ) { // chop off the end
a - > end = end ;
* next = a - > next ;
return 1 ;
}
if ( a - > end = = end ) { // chop off the start
a - > start = start ;
attrUnlink ( attr , a , next ) ;
* tail = a ;
return 1 ;
}
// we'll need to split the attribute into two
b = uiNew ( struct attr ) ;
b - > type = a - > type ;
b - > val = a - > val ;
b - > start = end ;
b - > end = a - > end ;
* tail = b ;
a - > end = start ;
* next = a - > next ;
return 2 ;
2016-12-16 22:31:04 -06:00
}
2016-12-17 22:07:48 -06:00
2016-12-18 13:06:37 -06:00
static void attrGrow ( struct attrlist * alist , struct attr * a , size_t start , size_t end )
{
struct attr * before ;
struct attr * unused ;
// 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 ;
// TODO could we just return next?
attrUnlink ( alist , a , & unused ) ;
for ( before = alist - > first ; before ! = NULL ; before = before - > next )
if ( before - > start > a - > start )
break ;
attrInsertBefore ( alist , a , before ) ;
}
2016-12-17 22:07:48 -06:00
void attrlistInsertAt ( struct attrlist * alist , uiAttribute type , uintptr_t val , size_t start , size_t end )
{
struct attr * a ;
struct attr * before ;
struct attr * tail = NULL ;
2016-12-18 10:49:54 -06:00
int split = 0 ;
2016-12-17 22:07:48 -06:00
// 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
2016-12-18 10:49:54 -06:00
if ( split )
2016-12-17 22:07:48 -06:00
goto next ;
// should we split this?
if ( before - > type ! = type )
goto next ;
lstart = start ;
lend = end ;
if ( ! attrRangeIntersects ( 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 this might cause problems with system specific attributes, if we support those; maybe also user-specific?
2016-12-18 13:06:37 -06:00
// TODO will this cause problems with fonts?
2016-12-18 13:11:12 -06:00
// 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?
2016-12-17 22:07:48 -06:00
if ( before - > val = = val ) {
2016-12-18 13:06:37 -06:00
attrGrow ( alist , a , start , end ) ;
2016-12-18 10:49:54 -06:00
return ;
2016-12-17 22:07:48 -06:00
}
// okay the values are different; we need to split apart
2016-12-18 10:49:54 -06:00
attrDropRange ( alist , a , start , end , & tail , & before ) ;
split = 1 ;
continue ;
2016-12-17 22:07:48 -06:00
next :
before = before - > next ;
}
// if we got here, we know we have to add the attribute before before
a = uiNew ( struct attr ) ;
a - > type = type ;
a - > val = val ;
a - > start = start ;
a - > end = end ;
2016-12-18 13:06:37 -06:00
attrInsertBefore ( alist , a , before ) ;
2016-12-17 22:07:48 -06:00
// 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 ;
2016-12-18 13:06:37 -06:00
attrInsertBefore ( alist , tail , before ) ;
2016-12-17 22:07:48 -06:00
}