43 CPArray _rangeEntries;
75 [
string setAttributedString:aString];
97 _string =
"" + aString;
98 _rangeEntries = [makeRangeEntry(CPMakeRange(0, _string.length), attributes)];
132 return _string.length;
136 - (unsigned)_indexOfEntryWithIndex:(
unsigned)anIndex
138 if (anIndex < 0 || anIndex > _string.length || anIndex === undefined)
142 var sortFunction =
function(index, entry)
154 return [_rangeEntries indexOfObject:anIndex inSortedRange:nil options:0 usingComparator:sortFunction];
176 - (
CPDictionary)attributesAtIndex:(CPUInteger)anIndex effectiveRange:(CPRangePointer)aRange
179 var entryIndex = [
self _indexOfEntryWithIndex:anIndex];
184 var matchingRange = _rangeEntries[entryIndex];
188 aRange.location = matchingRange.range.location;
189 aRange.length = matchingRange.range.length;
192 return matchingRange.attributes;
216 - (
CPDictionary)attributesAtIndex:(CPUInteger)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
218 var startingEntryIndex = [
self _indexOfEntryWithIndex:anIndex];
224 return _rangeEntries[startingEntryIndex].attributes;
226 if (
CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
228 aRange.location = rangeLimit.location;
229 aRange.length = rangeLimit.length;
231 return _rangeEntries[startingEntryIndex].attributes;
235 var nextRangeIndex = startingEntryIndex - 1,
236 currentEntry = _rangeEntries[startingEntryIndex],
237 comparisonDict = currentEntry.attributes;
239 while (nextRangeIndex >= 0)
241 var nextEntry = _rangeEntries[nextRangeIndex];
243 if (
CPMaxRange(nextEntry.range) > rangeLimit.location && [nextEntry.attributes isEqualToDictionary:comparisonDict])
245 currentEntry = nextEntry;
252 aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
255 currentEntry = _rangeEntries[startingEntryIndex];
256 nextRangeIndex = startingEntryIndex + 1;
258 while (nextRangeIndex < _rangeEntries.length)
260 var nextEntry = _rangeEntries[nextRangeIndex];
262 if (nextEntry.range.location <
CPMaxRange(rangeLimit) && [nextEntry.attributes isEqualToDictionary:comparisonDict])
264 currentEntry = nextEntry;
273 return comparisonDict;
292 - (id)attribute:(
CPString)attribute atIndex:(CPUInteger)index effectiveRange:(CPRangePointer)aRange
299 aRange.length = _string.length;
329 - (id)attribute:(
CPString)attribute atIndex:(CPUInteger)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
331 var startingEntryIndex = [
self _indexOfEntryWithIndex:anIndex];
333 if (startingEntryIndex ===
CPNotFound || !attribute)
337 return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
339 if (
CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
341 aRange.location = rangeLimit.location;
342 aRange.length = rangeLimit.length;
344 return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
348 var nextRangeIndex = startingEntryIndex - 1,
349 currentEntry = _rangeEntries[startingEntryIndex],
350 comparisonAttribute = [currentEntry.attributes objectForKey:attribute];
352 while (nextRangeIndex >= 0)
354 var nextEntry = _rangeEntries[nextRangeIndex];
356 if (
CPMaxRange(nextEntry.range) > rangeLimit.location &&
isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
358 currentEntry = nextEntry;
365 aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
368 currentEntry = _rangeEntries[startingEntryIndex];
369 nextRangeIndex = startingEntryIndex + 1;
371 while (nextRangeIndex < _rangeEntries.length)
373 var nextEntry = _rangeEntries[nextRangeIndex];
375 if (nextEntry.range.location <
CPMaxRange(rangeLimit) &&
isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
377 currentEntry = nextEntry;
386 return comparisonAttribute;
401 if (_string !== [aString
string])
408 length = _string.length;
413 ![myAttributes isEqualToDictionary:comparisonAttributes])
435 if (anObject ===
self)
438 if ([anObject isKindOfClass:[
self class]])
454 if (!aRange ||
CPMaxRange(aRange) > _string.length || aRange.location < 0)
456 reason:"tried to get attributedSubstring for an invalid range: "+(aRange?CPStringFromRange(aRange):"nil")];
458 var newString = [[
CPAttributedString alloc] initWithString:_string.substring(aRange.location, CPMaxRange(aRange))],
459 entryIndex = [
self _indexOfEntryWithIndex:aRange.location];
462 _CPRaiseRangeException(
self, _cmd, aRange.location, _string.length);
464 var currentRangeEntry = _rangeEntries[entryIndex],
467 newString._rangeEntries = [];
469 while (currentRangeEntry &&
CPMaxRange(currentRangeEntry.range) < lastIndex)
472 newEntry.range.location -= aRange.location;
474 if (newEntry.range.location < 0)
476 newEntry.range.length += newEntry.range.location;
477 newEntry.range.location = 0;
480 newString._rangeEntries.push(newEntry);
481 currentRangeEntry = _rangeEntries[++entryIndex];
484 if (currentRangeEntry)
488 newRangeEntry.range.length =
CPMaxRange(aRange) - newRangeEntry.range.location;
489 newRangeEntry.range.location -= aRange.location;
491 if (newRangeEntry.range.location < 0)
493 newRangeEntry.range.length += newRangeEntry.range.location;
494 newRangeEntry.range.location = 0;
497 newString._rangeEntries.push(newRangeEntry);
518 - (void)replaceCharactersInRange:(CPRange)aRange withString:(
CPString)aString
523 var startingIndex = [
self _indexOfEntryWithIndex:aRange.location];
526 _CPRaiseRangeException(
self, _cmd, aRange.location, _string.length);
528 var startingRangeEntry = _rangeEntries[startingIndex],
529 endingIndex = [
self _indexOfEntryWithIndex:MAX(CPMaxRange(aRange) - 1, 0)];
532 _CPRaiseRangeException(
self, _cmd, MAX(
CPMaxRange(aRange) - 1, 0), _string.length);
534 var endingRangeEntry = _rangeEntries[endingIndex],
535 additionalLength = aString.length - aRange.length;
537 _string = _string.substring(0, aRange.location) + aString + _string.substring(
CPMaxRange(aRange));
539 if (startingIndex === endingIndex)
540 startingRangeEntry.range.length += additionalLength;
544 endingRangeEntry.range.location =
CPMaxRange(aRange);
546 startingRangeEntry.range.length =
CPMaxRange(aRange) - startingRangeEntry.range.location;
548 _rangeEntries.splice(startingIndex, endingIndex - startingIndex);
551 endingIndex = startingIndex + 1;
553 while (endingIndex < _rangeEntries.length)
554 _rangeEntries[endingIndex++].range.location += additionalLength;
561 - (void)deleteCharactersInRange:(CPRange)aRange
580 var startingEntryIndex = [
self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
581 endingEntryIndex = [
self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
582 current = startingEntryIndex;
585 endingEntryIndex = _rangeEntries.length;
587 while (current < endingEntryIndex)
588 _rangeEntries[current++].attributes = [aDictionary
copy];
591 [
self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
606 var startingEntryIndex = [
self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
607 endingEntryIndex = [
self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
608 current = startingEntryIndex;
611 endingEntryIndex = _rangeEntries.length;
613 while (current < endingEntryIndex)
615 var keys = [aDictionary
allKeys],
616 count = [keys count];
619 [_rangeEntries[current].attributes setObject:[aDictionary
objectForKey:keys[count]] forKey:keys[count]];
625 [
self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
640 - (void)addAttribute:(
CPString)anAttribute value:(
id)aValue range:(CPRange)aRange
642 [
self addAttributes:@{ anAttribute: aValue } range:aRange];
651 - (void)removeAttribute:(
CPString)anAttribute range:(CPRange)aRange
653 var startingEntryIndex = [
self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
654 endingEntryIndex = [
self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
655 current = startingEntryIndex;
658 endingEntryIndex = _rangeEntries.length;
660 while (current < endingEntryIndex)
661 [_rangeEntries[current++].attributes removeObjectForKey:anAttribute];
664 [
self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
689 if (anIndex < 0 || anIndex > [
self length])
690 [
CPException raise:CPRangeException
reason:"tried to insert attributed string at an invalid index: "+anIndex];
692 var entryIndexOfNextEntry = [
self _indexOfRangeEntryForIndex:anIndex splitOnMaxIndex:YES],
693 otherRangeEntries = aString._rangeEntries,
694 length = [aString
length];
697 entryIndexOfNextEntry = _rangeEntries.length;
699 _string = _string.substring(0, anIndex) + aString._string + _string.substring(anIndex);
701 var current = entryIndexOfNextEntry;
702 while (current < _rangeEntries.length)
703 _rangeEntries[current++].range.location += length;
705 var newRangeEntryCount = otherRangeEntries.length,
708 while (index < newRangeEntryCount)
711 entryCopy.range.location += anIndex;
713 _rangeEntries.splice(entryIndexOfNextEntry - 1 + index, 0, entryCopy);
741 _string = aString._string;
745 count = aString._rangeEntries.
length;
747 for (; i < count; i++)
752 - (Number)_indexOfRangeEntryForIndex:(
unsigned)characterIndex splitOnMaxIndex:(BOOL)split
754 var index = [
self _indexOfEntryWithIndex:characterIndex];
759 var rangeEntry = _rangeEntries[index];
761 if (rangeEntry.range.location === characterIndex || (
CPMaxRange(rangeEntry.range) - 1 === characterIndex && !split))
765 _rangeEntries.splice(index, 1, newEntries[0], newEntries[1]);
771 - (void)_coalesceRangeEntriesFromIndex:(
unsigned)start toIndex:(
unsigned)end
775 if (end >= _rangeEntries.length)
776 end = _rangeEntries.length - 1;
778 while (current < end)
780 var a = _rangeEntries[current],
781 b = _rangeEntries[current + 1];
783 if ([a.attributes isEqualToDictionary:b.attributes])
785 a.range.length =
CPMaxRange(b.range) - a.range.location;
786 _rangeEntries.splice(current + 1, 1);
835 if ([a respondsToSelector:
@selector(isEqual:)] && [a isEqual:b])
843 return {range:aRange, attributes:[attributes
copy]};
856 aRangeEntry.range.length = anIndex - aRangeEntry.range.location;
857 newRangeEntry.range.location = anIndex;
858 newRangeEntry.range.length = cachedIndex - anIndex;
859 newRangeEntry.attributes = [newRangeEntry.attributes copy];
861 return [aRangeEntry, newRangeEntry];