00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import "CPObject.j"
00024 @import "CPString.j"
00025 @import "CPDictionary.j"
00026 @import "CPRange.j"
00027
00044 @implementation CPAttributedString : CPObject
00045 {
00046 CPString _string;
00047 CPArray _rangeEntries;
00048 }
00049
00050
00056 - (id)initWithString:(CPString)aString
00057 {
00058 return [self initWithString:aString attributes:nil];
00059 }
00060
00066 - (id)initWithAttributedString:(CPAttributedString)aString
00067 {
00068 var string = [self initWithString:"" attributes:nil];
00069
00070 [string setAttributedString:aString];
00071
00072 return string;
00073 }
00074
00083 - (id)initWithString:(CPString)aString attributes:(CPDictionary)attributes
00084 {
00085 self = [super init];
00086
00087 if (!attributes)
00088 attributes = [CPDictionary dictionary];
00089
00090 _string = ""+aString;
00091 _rangeEntries = [makeRangeEntry(CPMakeRange(0, _string.length), attributes)];
00092
00093 return self;
00094 }
00095
00096
00102 - (CPString)string
00103 {
00104 return _string;
00105 }
00106
00112 - (CPString)mutableString
00113 {
00114 return [self string];
00115 }
00116
00122 - (unsigned)length
00123 {
00124 return _string.length;
00125 }
00126
00127
00128 - (unsigned)_indexOfEntryWithIndex:(unsigned)anIndex
00129 {
00130 if (anIndex < 0 || anIndex > _string.length || anIndex === undefined)
00131 return CPNotFound;
00132
00133
00134 var sortFunction = function(index, entry)
00135 {
00136
00137 if (CPLocationInRange(index, entry.range))
00138 return CPOrderedSame;
00139 else if (CPMaxRange(entry.range) <= index)
00140 return CPOrderedDescending;
00141 else
00142 return CPOrderedAscending;
00143 }
00144
00145 return [_rangeEntries indexOfObject:anIndex sortedByFunction:sortFunction];
00146 }
00147
00148
00167 - (CPDictionary)attributesAtIndex:(unsigned)anIndex effectiveRange:(CPRangePointer)aRange
00168 {
00169
00170 var entryIndex = [self _indexOfEntryWithIndex:anIndex];
00171
00172 if (entryIndex == CPNotFound)
00173 return nil;
00174
00175 var matchingRange = _rangeEntries[entryIndex];
00176 if (aRange)
00177 {
00178 aRange.location = matchingRange.range.location;
00179 aRange.length = matchingRange.range.length;
00180 }
00181
00182 return matchingRange.attributes;
00183 }
00184
00206 - (CPDictionary)attributesAtIndex:(unsigned)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
00207 {
00208 var startingEntryIndex = [self _indexOfEntryWithIndex:anIndex];
00209
00210 if (startingEntryIndex == CPNotFound)
00211 return nil;
00212
00213 if (!aRange)
00214 return _rangeEntries[startingEntryIndex].attributes;
00215
00216 if (CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
00217 {
00218 aRange.location = rangeLimit.location;
00219 aRange.length = rangeLimit.length;
00220
00221 return _rangeEntries[startingEntryIndex].attributes;
00222 }
00223
00224
00225 var nextRangeIndex = startingEntryIndex - 1,
00226 currentEntry = _rangeEntries[startingEntryIndex],
00227 comparisonDict = currentEntry.attributes;
00228
00229 while (nextRangeIndex >= 0)
00230 {
00231 var nextEntry = _rangeEntries[nextRangeIndex];
00232
00233 if (CPMaxRange(nextEntry.range) > rangeLimit.location && [nextEntry.attributes isEqualToDictionary:comparisonDict])
00234 {
00235 currentEntry = nextEntry;
00236 nextRangeIndex--;
00237 }
00238 else
00239 break;
00240 }
00241
00242 aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
00243
00244
00245 currentEntry = _rangeEntries[startingEntryIndex];
00246 nextRangeIndex = startingEntryIndex + 1;
00247
00248 while (nextRangeIndex < _rangeEntries.length)
00249 {
00250 var nextEntry = _rangeEntries[nextRangeIndex];
00251
00252 if (nextEntry.range.location < CPMaxRange(rangeLimit) && [nextEntry.attributes isEqualToDictionary:comparisonDict])
00253 {
00254 currentEntry = nextEntry;
00255 nextRangeIndex++;
00256 }
00257 else
00258 break;
00259 }
00260
00261 aRange.length = MIN(CPMaxRange(currentEntry.range), CPMaxRange(rangeLimit)) - aRange.location;
00262
00263 return comparisonDict;
00264 }
00265
00282 - (id)attribute:(CPString)attribute atIndex:(unsigned)index effectiveRange:(CPRangePointer)aRange
00283 {
00284 if (!attribute)
00285 {
00286 if (aRange)
00287 {
00288 aRange.location = 0;
00289 aRange.length = _string.length;
00290 }
00291
00292 return nil;
00293 }
00294
00295 return [[self attributesAtIndex:index effectiveRange:aRange] valueForKey:attribute];
00296 }
00297
00319 - (id)attribute:(CPString)attribute atIndex:(unsigned)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
00320 {
00321
00322 var startingEntryIndex = [self _indexOfEntryWithIndex:anIndex];
00323
00324 if (startingEntryIndex == CPNotFound || !attribute)
00325 return nil;
00326
00327 if (!aRange)
00328 return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
00329
00330 if (CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
00331 {
00332 aRange.location = rangeLimit.location;
00333 aRange.length = rangeLimit.length;
00334
00335 return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
00336 }
00337
00338
00339 var nextRangeIndex = startingEntryIndex - 1,
00340 currentEntry = _rangeEntries[startingEntryIndex],
00341 comparisonAttribute = [currentEntry.attributes objectForKey:attribute];
00342
00343 while (nextRangeIndex >= 0)
00344 {
00345 var nextEntry = _rangeEntries[nextRangeIndex];
00346
00347 if (CPMaxRange(nextEntry.range) > rangeLimit.location && isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
00348 {
00349 currentEntry = nextEntry;
00350 nextRangeIndex--;
00351 }
00352 else
00353 break;
00354 }
00355
00356 aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
00357
00358
00359 currentEntry = _rangeEntries[startingEntryIndex];
00360 nextRangeIndex = startingEntryIndex + 1;
00361
00362 while (nextRangeIndex < _rangeEntries.length)
00363 {
00364 var nextEntry = _rangeEntries[nextRangeIndex];
00365
00366 if (nextEntry.range.location < CPMaxRange(rangeLimit) && isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
00367 {
00368 currentEntry = nextEntry;
00369 nextRangeIndex++;
00370 }
00371 else
00372 break;
00373 }
00374
00375 aRange.length = MIN(CPMaxRange(currentEntry.range), CPMaxRange(rangeLimit)) - aRange.location;
00376
00377 return comparisonAttribute;
00378 }
00379
00380
00387 - (BOOL)isEqualToAttributedString:(CPAttributedString)aString
00388 {
00389 if(!aString)
00390 return NO;
00391
00392 if(_string != [aString string])
00393 return NO;
00394
00395 var myRange = CPMakeRange(),
00396 comparisonRange = CPMakeRange(),
00397 myAttributes = [self attributesAtIndex:0 effectiveRange:myRange],
00398 comparisonAttributes = [aString attributesAtIndex:0 effectiveRange:comparisonRange],
00399 length = _string.length;
00400
00401 while (CPMaxRange(CPUnionRange(myRange, comparisonRange)) < length)
00402 {
00403 if (CPIntersectionRange(myRange, comparisonRange).length > 0 && ![myAttributes isEqualToDictionary:comparisonAttributes])
00404 return NO;
00405 if (CPMaxRange(myRange) < CPMaxRange(comparisonRange))
00406 myAttributes = [self attributesAtIndex:CPMaxRange(myRange) effectiveRange:myRange];
00407 else
00408 comparisonAttributes = [aString attributesAtIndex:CPMaxRange(comparisonRange) effectiveRange:comparisonRange];
00409 }
00410
00411 return YES;
00412 }
00413
00421 - (BOOL)isEqual:(id)anObject
00422 {
00423 if (anObject == self)
00424 return YES;
00425
00426 if ([anObject isKindOfClass:[self class]])
00427 return [self isEqualToAttributedString:anObject];
00428
00429 return NO;
00430 }
00431
00432
00440 - (CPAttributedString)attributedSubstringFromRange:(CPRange)aRange
00441 {
00442 if (!aRange || CPMaxRange(aRange) > _string.length || aRange.location < 0)
00443 [CPException raise:CPRangeException
00444 reason:"tried to get attributedSubstring for an invalid range: "+(aRange?CPStringFromRange(aRange):"nil")];
00445
00446 var newString = [[CPAttributedString alloc] initWithString:_string.substring(aRange.location, CPMaxRange(aRange))],
00447 entryIndex = [self _indexOfEntryWithIndex:aRange.location],
00448 currentRangeEntry = _rangeEntries[entryIndex],
00449 lastIndex = CPMaxRange(aRange);
00450
00451 newString._rangeEntries = [];
00452
00453 while (currentRangeEntry && CPMaxRange(currentRangeEntry.range) < lastIndex)
00454 {
00455 var newEntry = copyRangeEntry(currentRangeEntry);
00456 newEntry.range.location -= aRange.location;
00457
00458 if (newEntry.range.location < 0)
00459 {
00460 newEntry.range.length += newEntry.range.location;
00461 newEntry.range.location = 0;
00462 }
00463
00464 newString._rangeEntries.push(newEntry);
00465 currentRangeEntry = _rangeEntries[++entryIndex];
00466 }
00467
00468 if (currentRangeEntry)
00469 {
00470 var newRangeEntry = copyRangeEntry(currentRangeEntry);
00471
00472 newRangeEntry.range.length = CPMaxRange(aRange) - newRangeEntry.range.location;
00473 newRangeEntry.range.location -= aRange.location;
00474
00475 if (newRangeEntry.range.location < 0)
00476 {
00477 newRangeEntry.range.length += newRangeEntry.range.location;
00478 newRangeEntry.range.location = 0;
00479 }
00480
00481 newString._rangeEntries.push(newRangeEntry);
00482 }
00483
00484 return newString;
00485 }
00486
00487
00502 - (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString
00503 {
00504 [self beginEditing];
00505
00506 if (!aString)
00507 aString = "";
00508
00509 var startingIndex = [self _indexOfEntryWithIndex:aRange.location],
00510 startingRangeEntry = _rangeEntries[startingIndex],
00511 endingIndex = [self _indexOfEntryWithIndex:MAX(CPMaxRange(aRange)-1, 0)],
00512 endingRangeEntry = _rangeEntries[endingIndex],
00513 additionalLength = aString.length - aRange.length;
00514
00515 _string = _string.substring(0, aRange.location) + aString + _string.substring(CPMaxRange(aRange));
00516
00517 if (startingIndex == endingIndex)
00518 startingRangeEntry.range.length += additionalLength;
00519 else
00520 {
00521 endingRangeEntry.range.length = CPMaxRange(endingRangeEntry.range) - CPMaxRange(aRange);
00522 endingRangeEntry.range.location = CPMaxRange(aRange);
00523
00524 startingRangeEntry.range.length = CPMaxRange(aRange) - startingRangeEntry.range.location;
00525
00526 _rangeEntries.splice(startingIndex, endingIndex - startingIndex);
00527 }
00528
00529 endingIndex = startingIndex + 1;
00530
00531 while(endingIndex < _rangeEntries.length)
00532 _rangeEntries[endingIndex++].range.location+=additionalLength;
00533
00534 [self endEditing];
00535 }
00536
00541 - (void)deleteCharactersInRange:(CPRange)aRange
00542 {
00543 [self replaceCharactersInRange:aRange withString:nil];
00544 }
00545
00546
00558 - (void)setAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
00559 {
00560 [self beginEditing];
00561
00562 var startingEntryIndex = [self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
00563 endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
00564 current = startingEntryIndex;
00565
00566 if (endingEntryIndex == CPNotFound)
00567 endingEntryIndex = _rangeEntries.length;
00568
00569 while (current < endingEntryIndex)
00570 _rangeEntries[current++].attributes = [aDictionary copy];
00571
00572
00573 [self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
00574
00575 [self endEditing];
00576 }
00577
00588 - (void)addAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
00589 {
00590 [self beginEditing];
00591
00592 var startingEntryIndex = [self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
00593 endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
00594 current = startingEntryIndex;
00595
00596 if (endingEntryIndex == CPNotFound)
00597 endingEntryIndex = _rangeEntries.length;
00598
00599 while (current < endingEntryIndex)
00600 {
00601 var keys = [aDictionary allKeys],
00602 count = [keys count];
00603
00604 while (count--)
00605 [_rangeEntries[current].attributes setObject:[aDictionary objectForKey:keys[count]] forKey:keys[count]];
00606
00607 current++;
00608 }
00609
00610
00611 [self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
00612
00613 [self endEditing];
00614 }
00615
00628 - (void)addAttribute:(CPString)anAttribute value:(id)aValue range:(CPRange)aRange
00629 {
00630 [self addAttributes:[CPDictionary dictionaryWithObject:aValue forKey:anAttribute] range:aRange];
00631 }
00632
00639 - (void)removeAttribute:(CPString)anAttribute range:(CPRange)aRange
00640 {
00641 [self beginEditing];
00642
00643 var startingEntryIndex = [self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
00644 endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
00645 current = startingEntryIndex;
00646
00647 if (endingEntryIndex == CPNotFound)
00648 endingEntryIndex = _rangeEntries.length;
00649
00650 while (current < endingEntryIndex)
00651 [_rangeEntries[current++].attributes removeObjectForKey:anAttribute];
00652
00653
00654 [self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
00655
00656 [self endEditing];
00657 }
00658
00659
00665 - (void)appendAttributedString:(CPAttributedString)aString
00666 {
00667 [self insertAttributedString:aString atIndex:_string.length];
00668 }
00669
00679 - (void)insertAttributedString:(CPAttributedString)aString atIndex:(unsigned)anIndex
00680 {
00681 [self beginEditing];
00682
00683 if (anIndex < 0 || anIndex > [self length])
00684 [CPException raise:CPRangeException reason:"tried to insert attributed string at an invalid index: "+anIndex];
00685
00686 var entryIndexOfNextEntry = [self _indexOfRangeEntryForIndex:anIndex splitOnMaxIndex:YES],
00687 otherRangeEntries = aString._rangeEntries,
00688 length = [aString length];
00689
00690 if (entryIndexOfNextEntry == CPNotFound)
00691 entryIndexOfNextEntry = _rangeEntries.length;
00692
00693 _string = _string.substring(0, anIndex) + aString._string + _string.substring(anIndex);
00694
00695 var current = entryIndexOfNextEntry;
00696 while (current < _rangeEntries.length)
00697 _rangeEntries[current++].range.location += length;
00698
00699 var newRangeEntryCount = otherRangeEntries.length,
00700 index = 0;
00701
00702 while (index < newRangeEntryCount)
00703 {
00704 var entryCopy = copyRangeEntry(otherRangeEntries[index++]);
00705 entryCopy.range.location += anIndex;
00706
00707 _rangeEntries.splice(entryIndexOfNextEntry-1+index, 0, entryCopy);
00708 }
00709
00710
00711
00712
00713 [self endEditing];
00714 }
00715
00724 - (void)replaceCharactersInRange:(CPRange)aRange withAttributedString:(CPAttributedString)aString
00725 {
00726 [self beginEditing];
00727
00728 [self deleteCharactersInRange:aRange];
00729 [self insertAttributedString:aString atIndex:aRange.location];
00730
00731 [self endEditing];
00732 }
00733
00739 - (void)setAttributedString:(CPAttributedString)aString
00740 {
00741 [self beginEditing];
00742
00743 _string = aString._string;
00744 _rangeEntries = [];
00745
00746 for (var i=0, count = aString._rangeEntries.length; i<count; i++)
00747 _rangeEntries.push(copyRangeEntry(aString._rangeEntries[i]));
00748
00749 [self endEditing];
00750 }
00751
00752
00753 - (Number)_indexOfRangeEntryForIndex:(unsigned)characterIndex splitOnMaxIndex:(BOOL)split
00754 {
00755 var index = [self _indexOfEntryWithIndex:characterIndex];
00756
00757 if (index < 0)
00758 return index;
00759
00760 var rangeEntry = _rangeEntries[index];
00761
00762 if (rangeEntry.range.location == characterIndex || (CPMaxRange(rangeEntry.range) - 1 == characterIndex && !split))
00763 return index;
00764
00765 var newEntries = splitRangeEntryAtIndex(rangeEntry, characterIndex);
00766 _rangeEntries.splice(index, 1, newEntries[0], newEntries[1]);
00767 index++;
00768
00769 return index;
00770 }
00771
00772 - (void)_coalesceRangeEntriesFromIndex:(unsigned)start toIndex:(unsigned)end
00773 {
00774 var current = start;
00775
00776 if (end >= _rangeEntries.length)
00777 end = _rangeEntries.length -1;
00778
00779 while (current < end)
00780 {
00781 var a = _rangeEntries[current],
00782 b = _rangeEntries[current+1];
00783
00784 if ([a.attributes isEqualToDictionary:b.attributes])
00785 {
00786 a.range.length = CPMaxRange(b.range) - a.range.location;
00787 _rangeEntries.splice(current+1, 1);
00788 end--;
00789 }
00790 else
00791 current++;
00792 }
00793 }
00794
00795
00800 - (void)beginEditing
00801 {
00802
00803 }
00804
00809 - (void)endEditing
00810 {
00811
00812 }
00813
00814 @end
00815
00824 @implementation CPMutableAttributedString : CPAttributedString {}
00825 @end
00826
00827 var isEqual = function isEqual(a, b)
00828 {
00829 if (a == b)
00830 return YES;
00831
00832 if ([a respondsToSelector:@selector(isEqual:)] && [a isEqual:b])
00833 return YES;
00834
00835 return NO;
00836 }
00837
00838 var makeRangeEntry = function makeRangeEntry(aRange, attributes)
00839 {
00840 return {range:aRange, attributes:[attributes copy]};
00841 }
00842
00843 var copyRangeEntry = function copyRangeEntry(aRangeEntry)
00844 {
00845 return makeRangeEntry(CPCopyRange(aRangeEntry.range), [aRangeEntry.attributes copy]);
00846 }
00847
00848 var splitRangeEntry = function splitRangeEntryAtIndex(aRangeEntry, anIndex)
00849 {
00850 var newRangeEntry = copyRangeEntry(aRangeEntry),
00851 cachedIndex = CPMaxRange(aRangeEntry.range);
00852
00853 aRangeEntry.range.length = anIndex - aRangeEntry.range.location;
00854 newRangeEntry.range.location = anIndex;
00855 newRangeEntry.range.length = cachedIndex - anIndex;
00856 newRangeEntry.attributes = [newRangeEntry.attributes copy];
00857
00858 return [aRangeEntry, newRangeEntry];
00859 }