42 CPArray _rangeEntries;
74 [
string setAttributedString:aString];
96 _string =
"" + aString;
97 _rangeEntries = [makeRangeEntry(CPMakeRange(0, _string.length), attributes)];
135 - (unsigned)_indexOfEntryWithIndex:(
unsigned)anIndex
137 if (anIndex < 0 || anIndex > _string.length || anIndex === undefined)
141 var sortFunction =
function(index, entry)
153 return [_rangeEntries indexOfObject:anIndex inSortedRange:nil options:0 usingComparator:sortFunction];
175 - (
CPDictionary)attributesAtIndex:(CPUInteger)anIndex effectiveRange:(CPRangePointer)aRange
178 var entryIndex = [
self _indexOfEntryWithIndex:anIndex];
183 var matchingRange = _rangeEntries[entryIndex];
187 aRange.location = matchingRange.range.location;
188 aRange.length = matchingRange.range.length;
191 return matchingRange.attributes;
215 - (
CPDictionary)attributesAtIndex:(CPUInteger)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
217 var startingEntryIndex = [
self _indexOfEntryWithIndex:anIndex];
223 return _rangeEntries[startingEntryIndex].attributes;
225 if (
CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
227 aRange.location = rangeLimit.location;
228 aRange.length = rangeLimit.length;
230 return _rangeEntries[startingEntryIndex].attributes;
234 var nextRangeIndex = startingEntryIndex - 1,
235 currentEntry = _rangeEntries[startingEntryIndex],
236 comparisonDict = currentEntry.attributes;
238 while (nextRangeIndex >= 0)
240 var nextEntry = _rangeEntries[nextRangeIndex];
242 if (
CPMaxRange(nextEntry.range) > rangeLimit.location && [nextEntry.attributes isEqualToDictionary:comparisonDict])
244 currentEntry = nextEntry;
251 aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
254 currentEntry = _rangeEntries[startingEntryIndex];
255 nextRangeIndex = startingEntryIndex + 1;
257 while (nextRangeIndex < _rangeEntries.length)
259 var nextEntry = _rangeEntries[nextRangeIndex];
261 if (nextEntry.range.location <
CPMaxRange(rangeLimit) && [nextEntry.attributes isEqualToDictionary:comparisonDict])
263 currentEntry = nextEntry;
272 return comparisonDict;
291 - (id)attribute:(
CPString)attribute atIndex:(CPUInteger)index effectiveRange:(CPRangePointer)aRange
298 aRange.
length = _string.length;
328 - (id)attribute:(
CPString)attribute atIndex:(CPUInteger)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
330 var startingEntryIndex = [
self _indexOfEntryWithIndex:anIndex];
332 if (startingEntryIndex ===
CPNotFound || !attribute)
336 return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
338 if (
CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
340 aRange.location = rangeLimit.location;
341 aRange.
length = rangeLimit.length;
343 return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
347 var nextRangeIndex = startingEntryIndex - 1,
348 currentEntry = _rangeEntries[startingEntryIndex],
349 comparisonAttribute = [currentEntry.attributes objectForKey:attribute];
351 while (nextRangeIndex >= 0)
353 var nextEntry = _rangeEntries[nextRangeIndex];
355 if (
CPMaxRange(nextEntry.range) > rangeLimit.location &&
isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
357 currentEntry = nextEntry;
364 aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
367 currentEntry = _rangeEntries[startingEntryIndex];
368 nextRangeIndex = startingEntryIndex + 1;
370 while (nextRangeIndex < _rangeEntries.length)
372 var nextEntry = _rangeEntries[nextRangeIndex];
374 if (nextEntry.range.location <
CPMaxRange(rangeLimit) &&
isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
376 currentEntry = nextEntry;
385 return comparisonAttribute;
400 if (_string !== [aString
string])
407 length = _string.length;
412 ![myAttributes isEqualToDictionary:comparisonAttributes])
434 if (anObject ===
self)
437 if ([anObject isKindOfClass:[
self class]])
453 if (!aRange ||
CPMaxRange(aRange) > _string.length || aRange.location < 0)
455 reason:"tried to get attributedSubstring for an invalid range: "+(aRange?CPStringFromRange(aRange):"nil")];
457 var newString = [[
CPAttributedString alloc] initWithString:_string.substring(aRange.location, CPMaxRange(aRange))],
458 entryIndex = [
self _indexOfEntryWithIndex:aRange.location];
461 _CPRaiseRangeException(
self, _cmd, aRange.location, _string.length);
463 var currentRangeEntry = _rangeEntries[entryIndex],
466 newString._rangeEntries = [];
468 while (currentRangeEntry &&
CPMaxRange(currentRangeEntry.range) < lastIndex)
471 newEntry.range.location -= aRange.location;
473 if (newEntry.range.location < 0)
475 newEntry.range.length += newEntry.range.location;
476 newEntry.range.location = 0;
479 newString._rangeEntries.push(newEntry);
480 currentRangeEntry = _rangeEntries[++entryIndex];
483 if (currentRangeEntry)
487 newRangeEntry.range.length =
CPMaxRange(aRange) - newRangeEntry.range.location;
488 newRangeEntry.range.location -= aRange.location;
490 if (newRangeEntry.range.location < 0)
492 newRangeEntry.range.length += newRangeEntry.range.location;
493 newRangeEntry.range.location = 0;
496 newString._rangeEntries.push(newRangeEntry);
517 - (void)replaceCharactersInRange:(CPRange)aRange withString:(
CPString)aString
522 var lastValidIndex = MAX(_rangeEntries.length - 1, 0),
523 startingIndex = [
self _indexOfEntryWithIndex:aRange.location];
525 if (startingIndex < 0)
526 startingIndex = lastValidIndex;
528 var endingIndex = [
self _indexOfEntryWithIndex:CPMaxRange(aRange)];
531 endingIndex = lastValidIndex;
533 var additionalLength = aString.
length - aRange.length,
534 patchPosition = startingIndex;
536 _string = _string.substring(0, aRange.location) + aString + _string.substring(
CPMaxRange(aRange));
537 var originalLength = _rangeEntries[patchPosition].range.length;
539 if (startingIndex === endingIndex)
540 _rangeEntries[patchPosition].range.length += additionalLength;
543 if (
CPIntersectionRange(_rangeEntries[patchPosition].range, aRange).length < originalLength)
548 if (endingIndex > startingIndex)
550 var originalOffset= _rangeEntries[startingIndex].range.location,
551 offsetFromSplicing =
CPMaxRange(_rangeEntries[endingIndex].range) - originalOffset;
552 _rangeEntries.splice(startingIndex, endingIndex - startingIndex);
553 _rangeEntries[startingIndex].range =
CPMakeRange(originalOffset, offsetFromSplicing);
556 if (patchPosition !== startingIndex)
559 _rangeEntries[patchPosition].range.length = originalLength + lhsOffset;
561 _rangeEntries[startingIndex].range.location += lhsOffset;
562 _rangeEntries[startingIndex].range.length += rhsOffset;
563 patchPosition = startingIndex;
565 _rangeEntries[patchPosition].range.length += additionalLength;
568 for (var patchIndex = patchPosition + 1, l = _rangeEntries.length; patchIndex < l; patchIndex++)
569 _rangeEntries[patchIndex].range.location += additionalLength;
576 - (void)deleteCharactersInRange:(CPRange)aRange
595 var startingEntryIndex = [
self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
596 endingEntryIndex = [
self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
597 current = startingEntryIndex;
600 current = MAX(_rangeEntries.length - 1, 0);
603 endingEntryIndex = _rangeEntries.length;
605 while (current < endingEntryIndex)
606 _rangeEntries[current++].attributes = [aDictionary
copy];
609 [
self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
624 var startingEntryIndex = [
self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
625 endingEntryIndex = [
self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
626 current = startingEntryIndex;
629 endingEntryIndex = _rangeEntries.length;
631 while (current < endingEntryIndex)
633 var keys = [aDictionary
allKeys],
634 count = [keys count];
637 [_rangeEntries[current].attributes setObject:[aDictionary
objectForKey:keys[count]] forKey:keys[count]];
643 [
self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
658 - (void)addAttribute:(
CPString)anAttribute value:(
id)aValue range:(CPRange)aRange
669 - (void)removeAttribute:(
CPString)anAttribute range:(CPRange)aRange
671 var startingEntryIndex = [
self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
672 endingEntryIndex = [
self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
673 current = startingEntryIndex;
676 endingEntryIndex = _rangeEntries.length;
678 while (current < endingEntryIndex)
679 [_rangeEntries[current++].attributes removeObjectForKey:anAttribute];
682 [
self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
707 if (anIndex < 0 || anIndex > [
self length])
708 [
CPException raise:CPRangeException
reason:"tried to insert attributed string at an invalid index: " + anIndex];
710 var entryIndexOfNextEntry = [
self _indexOfRangeEntryForIndex:anIndex splitOnMaxIndex:YES],
711 otherRangeEntries = aString._rangeEntries,
712 length = [aString
length];
715 entryIndexOfNextEntry = _rangeEntries.length;
717 _string = _string.substring(0, anIndex) + aString._string + _string.substring(anIndex);
719 var current = entryIndexOfNextEntry;
720 while (current < _rangeEntries.length)
721 _rangeEntries[current++].range.location += length;
723 var newRangeEntryCount = otherRangeEntries.length,
726 while (index < newRangeEntryCount)
729 entryCopy.range.location += anIndex;
731 _rangeEntries.splice(entryIndexOfNextEntry - 1 + index, 0, entryCopy);
734 [
self _coalesceRangeEntriesFromIndex:MAX(0, entryIndexOfNextEntry - 1) toIndex:MIN(entryIndexOfNextEntry + 1, _rangeEntries.length - 1)];
758 _string = aString._string;
759 _rangeEntries = [aString._rangeEntries arrayByApplyingBlock:function(entry)
761 return copyRangeEntry(entry);
766 - (Number)_indexOfRangeEntryForIndex:(
unsigned)characterIndex splitOnMaxIndex:(BOOL)split
768 var index = [
self _indexOfEntryWithIndex:characterIndex];
773 var rangeEntry = _rangeEntries[index];
775 if (rangeEntry.range.location === characterIndex || (
CPMaxRange(rangeEntry.range) - 1 === characterIndex && !split))
779 _rangeEntries.splice(index, 1, newEntries[0], newEntries[1]);
785 - (void)_coalesceRangeEntriesFromIndex:(
unsigned)start toIndex:(
unsigned)end
789 if (end >= _rangeEntries.length)
790 end = _rangeEntries.length - 1;
792 while (current < end)
794 var a = _rangeEntries[current],
795 b = _rangeEntries[current + 1];
797 if (a && b && [a.attributes isEqualToDictionary:b.attributes])
799 a.range.length =
CPMaxRange(b.range) - a.range.location;
800 _rangeEntries.splice(current + 1, 1);
841 _string = [aCoder decodeObjectForKey:CPAttributedStringStringKey];
842 var decodedRanges = [aCoder decodeObjectForKey:CPAttributedStringRangesKey],
843 decodedAttributes = [aCoder decodeObjectForKey:CPAttributedStringAttributesKey];
847 for (var i = 0, l = decodedRanges.length; i < l; i++)
848 _rangeEntries.push(
makeRangeEntry(decodedRanges[i], decodedAttributes[i]));
856 [aCoder encodeObject:_string forKey:CPAttributedStringStringKey];
858 var rangesForEncoding = [],
859 dictsForEncoding = [];
861 for (var i = 0, l = _rangeEntries.length; i < l; i++)
863 rangesForEncoding.push(_rangeEntries[i].range);
864 dictsForEncoding.push(_rangeEntries[i].attributes);
867 [aCoder encodeObject:rangesForEncoding forKey:CPAttributedStringRangesKey];
868 [aCoder encodeObject:dictsForEncoding forKey:CPAttributedStringAttributesKey];
892 if ([a respondsToSelector:
@selector(
isEqual:)] && [a isEqual:b])
900 return {range:aRange, attributes:[attributes
copy]};
913 aRangeEntry.range.length = anIndex - aRangeEntry.range.location;
914 newRangeEntry.range.location = anIndex;
915 newRangeEntry.range.length = cachedIndex - anIndex;
916 newRangeEntry.attributes = [newRangeEntry.attributes copy];
918 return [aRangeEntry, newRangeEntry];
var CPAttributedStringRangesKey
Used to implement exception handling (creating & raising).
function CPUnionRange(lhsRange, rhsRange)
var splitRangeEntryAtIndex
CPDictionary attributesAtIndex:effectiveRange:(CPUInteger anIndex, [effectiveRange] CPRangePointer aRange)
void raise:reason:(CPString aName, [reason] CPString aReason)
void addAttributes:range:(CPDictionary aDictionary, [range] CPRange aRange)
function CPRangeInRange(lhsRange, rhsRange)
A mutable key-value pair collection.
A mutable character string with attributes.
var CPAttributedStringAttributesKey
function CPMaxRange(aRange)
An immutable string (collection of characters).
id objectForKey:(id aKey)
BOOL isEqualToAttributedString:(CPAttributedString aString)
function CPIntersectionRange(lhsRange, rhsRange)
id initWithString:attributes:(CPString aString, [attributes] CPDictionary attributes)
function CPMakeRangeCopy(aRange)
void insertAttributedString:atIndex:(CPAttributedString aString, [atIndex] CPUInteger anIndex)
Defines methods for use when archiving & restoring (enc/decoding).
id valueForKey:(CPString aKey)
function CPLocationInRange(aLocation, aRange)
FrameUpdater prototype start
var CPAttributedStringStringKey
void replaceCharactersInRange:withString:(CPRange aRange, [withString] CPString aString)
void deleteCharactersInRange:(CPRange aRange)
CPRange function CPMakeRange(location, length)