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
00043 @implementation CPAttributedString : CPObject
00044 {
00045 CPString _string;
00046 CPArray _rangeEntries;
00047 }
00048
00049
00055 - (id)initWithString:(CPString)aString
00056 {
00057 return [self initWithString:aString attributes:nil];
00058 }
00059
00065 - (id)initWithAttributedString:(CPAttributedString)aString
00066 {
00067 var string = [self initWithString:"" attributes:nil];
00068
00069 [string setAttributedString:aString];
00070
00071 return string;
00072 }
00073
00082 - (id)initWithString:(CPString)aString attributes:(CPDictionary)attributes
00083 {
00084 self = [super init];
00085
00086 if (!attributes)
00087 attributes = [CPDictionary dictionary];
00088
00089 _string = ""+aString;
00090 _rangeEntries = [makeRangeEntry(CPMakeRange(0, _string.length), attributes)];
00091
00092 return self;
00093 }
00094
00095
00101 - (CPString)string
00102 {
00103 return _string;
00104 }
00105
00111 - (CPString)mutableString
00112 {
00113 return [self string];
00114 }
00115
00121 - (unsigned)length
00122 {
00123 return _string.length;
00124 }
00125
00126
00127 - (unsigned)_indexOfEntryWithIndex:(unsigned)anIndex
00128 {
00129 if (anIndex < 0 || anIndex > _string.length || anIndex === undefined)
00130 return CPNotFound;
00131
00132
00133 var sortFunction = function(index, entry)
00134 {
00135
00136 if (CPLocationInRange(index, entry.range))
00137 return CPOrderedSame;
00138 else if (CPMaxRange(entry.range) <= index)
00139 return CPOrderedDescending;
00140 else
00141 return CPOrderedAscending;
00142 }
00143
00144 return [_rangeEntries indexOfObject:anIndex sortedByFunction:sortFunction];
00145 }
00146
00147
00166 - (CPDictionary)attributesAtIndex:(unsigned)anIndex effectiveRange:(CPRangePointer)aRange
00167 {
00168
00169 var entryIndex = [self _indexOfEntryWithIndex:anIndex];
00170
00171 if (entryIndex == CPNotFound)
00172 return nil;
00173
00174 var matchingRange = _rangeEntries[entryIndex];
00175 if (aRange)
00176 {
00177 aRange.location = matchingRange.range.location;
00178 aRange.length = matchingRange.range.length;
00179 }
00180
00181 return matchingRange.attributes;
00182 }
00183
00205 - (CPDictionary)attributesAtIndex:(unsigned)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
00206 {
00207 var startingEntryIndex = [self _indexOfEntryWithIndex:anIndex];
00208
00209 if (startingEntryIndex == CPNotFound)
00210 return nil;
00211
00212 if (!aRange)
00213 return _rangeEntries[startingEntryIndex].attributes;
00214
00215 if (CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
00216 {
00217 aRange.location = rangeLimit.location;
00218 aRange.length = rangeLimit.length;
00219
00220 return _rangeEntries[startingEntryIndex].attributes;
00221 }
00222
00223
00224 var nextRangeIndex = startingEntryIndex - 1,
00225 currentEntry = _rangeEntries[startingEntryIndex],
00226 comparisonDict = currentEntry.attributes;
00227
00228 while (nextRangeIndex >= 0)
00229 {
00230 var nextEntry = _rangeEntries[nextRangeIndex];
00231
00232 if (CPMaxRange(nextEntry.range) > rangeLimit.location && [nextEntry.attributes isEqualToDictionary:comparisonDict])
00233 {
00234 currentEntry = nextEntry;
00235 nextRangeIndex--;
00236 }
00237 else
00238 break;
00239 }
00240
00241 aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
00242
00243
00244 currentEntry = _rangeEntries[startingEntryIndex];
00245 nextRangeIndex = startingEntryIndex + 1;
00246
00247 while (nextRangeIndex < _rangeEntries.length)
00248 {
00249 var nextEntry = _rangeEntries[nextRangeIndex];
00250
00251 if (nextEntry.range.location < CPMaxRange(rangeLimit) && [nextEntry.attributes isEqualToDictionary:comparisonDict])
00252 {
00253 currentEntry = nextEntry;
00254 nextRangeIndex++;
00255 }
00256 else
00257 break;
00258 }
00259
00260 aRange.length = MIN(CPMaxRange(currentEntry.range), CPMaxRange(rangeLimit)) - aRange.location;
00261
00262 return comparisonDict;
00263 }
00264
00281 - (id)attribute:(CPString)attribute atIndex:(unsigned)index effectiveRange:(CPRangePointer)aRange
00282 {
00283 if (!attribute)
00284 {
00285 if (aRange)
00286 {
00287 aRange.location = 0;
00288 aRange.length = _string.length;
00289 }
00290
00291 return nil;
00292 }
00293
00294 return [[self attributesAtIndex:index effectiveRange:aRange] valueForKey:attribute];
00295 }
00296
00318 - (id)attribute:(CPString)attribute atIndex:(unsigned)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
00319 {
00320
00321 var startingEntryIndex = [self _indexOfEntryWithIndex:anIndex];
00322
00323 if (startingEntryIndex == CPNotFound || !attribute)
00324 return nil;
00325
00326 if (!aRange)
00327 return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
00328
00329 if (CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
00330 {
00331 aRange.location = rangeLimit.location;
00332 aRange.length = rangeLimit.length;
00333
00334 return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
00335 }
00336
00337
00338 var nextRangeIndex = startingEntryIndex - 1,
00339 currentEntry = _rangeEntries[startingEntryIndex],
00340 comparisonAttribute = [currentEntry.attributes objectForKey:attribute];
00341
00342 while (nextRangeIndex >= 0)
00343 {
00344 var nextEntry = _rangeEntries[nextRangeIndex];
00345
00346 if (CPMaxRange(nextEntry.range) > rangeLimit.location && isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
00347 {
00348 currentEntry = nextEntry;
00349 nextRangeIndex--;
00350 }
00351 else
00352 break;
00353 }
00354
00355 aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
00356
00357
00358 currentEntry = _rangeEntries[startingEntryIndex];
00359 nextRangeIndex = startingEntryIndex + 1;
00360
00361 while (nextRangeIndex < _rangeEntries.length)
00362 {
00363 var nextEntry = _rangeEntries[nextRangeIndex];
00364
00365 if (nextEntry.range.location < CPMaxRange(rangeLimit) && isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
00366 {
00367 currentEntry = nextEntry;
00368 nextRangeIndex++;
00369 }
00370 else
00371 break;
00372 }
00373
00374 aRange.length = MIN(CPMaxRange(currentEntry.range), CPMaxRange(rangeLimit)) - aRange.location;
00375
00376 return comparisonAttribute;
00377 }
00378
00379
00386 - (BOOL)isEqualToAttributedString:(CPAttributedString)aString
00387 {
00388 if(!aString)
00389 return NO;
00390
00391 if(_string != [aString string])
00392 return NO;
00393
00394 var myRange = CPMakeRange(),
00395 comparisonRange = CPMakeRange(),
00396 myAttributes = [self attributesAtIndex:0 effectiveRange:myRange],
00397 comparisonAttributes = [aString attributesAtIndex:0 effectiveRange:comparisonRange],
00398 length = _string.length;
00399
00400 while (CPMaxRange(CPUnionRange(myRange, comparisonRange)) < length)
00401 {
00402 if (CPIntersectionRange(myRange, comparisonRange).length > 0 && ![myAttributes isEqualToDictionary:comparisonAttributes])
00403 return NO;
00404 if (CPMaxRange(myRange) < CPMaxRange(comparisonRange))
00405 myAttributes = [self attributesAtIndex:CPMaxRange(myRange) effectiveRange:myRange];
00406 else
00407 comparisonAttributes = [aString attributesAtIndex:CPMaxRange(comparisonRange) effectiveRange:comparisonRange];
00408 }
00409
00410 return YES;
00411 }
00412
00420 - (BOOL)isEqual:(id)anObject
00421 {
00422 if (anObject == self)
00423 return YES;
00424
00425 if ([anObject isKindOfClass:[self class]])
00426 return [self isEqualToAttributedString:anObject];
00427
00428 return NO;
00429 }
00430
00431
00439 - (CPAttributedString)attributedSubstringFromRange:(CPRange)aRange
00440 {
00441 if (!aRange || CPMaxRange(aRange) > _string.length || aRange.location < 0)
00442 [CPException raise:CPRangeException
00443 reason:"tried to get attributedSubstring for an invalid range: "+(aRange?CPStringFromRange(aRange):"nil")];
00444
00445 var newString = [[CPAttributedString alloc] initWithString:_string.substring(aRange.location, CPMaxRange(aRange))],
00446 entryIndex = [self _indexOfEntryWithIndex:aRange.location],
00447 currentRangeEntry = _rangeEntries[entryIndex],
00448 lastIndex = CPMaxRange(aRange);
00449
00450 newString._rangeEntries = [];
00451
00452 while (currentRangeEntry && CPMaxRange(currentRangeEntry.range) < lastIndex)
00453 {
00454 var newEntry = copyRangeEntry(currentRangeEntry);
00455 newEntry.range.location -= aRange.location;
00456
00457 if (newEntry.range.location < 0)
00458 {
00459 newEntry.range.length += newEntry.range.location;
00460 newEntry.range.location = 0;
00461 }
00462
00463 newString._rangeEntries.push(newEntry);
00464 currentRangeEntry = _rangeEntries[++entryIndex];
00465 }
00466
00467 if (currentRangeEntry)
00468 {
00469 var newRangeEntry = copyRangeEntry(currentRangeEntry);
00470
00471 newRangeEntry.range.length = CPMaxRange(aRange) - newRangeEntry.range.location;
00472 newRangeEntry.range.location -= aRange.location;
00473
00474 if (newRangeEntry.range.location < 0)
00475 {
00476 newRangeEntry.range.length += newRangeEntry.range.location;
00477 newRangeEntry.range.location = 0;
00478 }
00479
00480 newString._rangeEntries.push(newRangeEntry);
00481 }
00482
00483 return newString;
00484 }
00485
00486
00498 - (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString
00499 {
00500 [self beginEditing];
00501
00502 if (!aString)
00503 aString = "";
00504
00505 var startingIndex = [self _indexOfEntryWithIndex:aRange.location],
00506 startingRangeEntry = _rangeEntries[startingIndex],
00507 endingIndex = [self _indexOfEntryWithIndex:MAX(CPMaxRange(aRange)-1, 0)],
00508 endingRangeEntry = _rangeEntries[endingIndex],
00509 additionalLength = aString.length - aRange.length;
00510
00511 _string = _string.substring(0, aRange.location) + aString + _string.substring(CPMaxRange(aRange));
00512
00513 if (startingIndex == endingIndex)
00514 startingRangeEntry.range.length += additionalLength;
00515 else
00516 {
00517 endingRangeEntry.range.length = CPMaxRange(endingRangeEntry.range) - CPMaxRange(aRange);
00518 endingRangeEntry.range.location = CPMaxRange(aRange);
00519
00520 startingRangeEntry.range.length = CPMaxRange(aRange) - startingRangeEntry.range.location;
00521
00522 _rangeEntries.splice(startingIndex, endingIndex - startingIndex);
00523 }
00524
00525 endingIndex = startingIndex + 1;
00526
00527 while(endingIndex < _rangeEntries.length)
00528 _rangeEntries[endingIndex++].range.location+=additionalLength;
00529
00530 [self endEditing];
00531 }
00532
00537 - (void)deleteCharactersInRange:(CPRange)aRange
00538 {
00539 [self replaceCharactersInRange:aRange withString:nil];
00540 }
00541
00542
00554 - (void)setAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
00555 {
00556 [self beginEditing];
00557
00558 var startingEntryIndex = [self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
00559 endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
00560 current = startingEntryIndex;
00561
00562 if (endingEntryIndex == CPNotFound)
00563 endingEntryIndex = _rangeEntries.length;
00564
00565 while (current < endingEntryIndex)
00566 _rangeEntries[current++].attributes = [aDictionary copy];
00567
00568
00569 [self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
00570
00571 [self endEditing];
00572 }
00573
00584 - (void)addAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
00585 {
00586 [self beginEditing];
00587
00588 var startingEntryIndex = [self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
00589 endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
00590 current = startingEntryIndex;
00591
00592 if (endingEntryIndex == CPNotFound)
00593 endingEntryIndex = _rangeEntries.length;
00594
00595 while (current < endingEntryIndex)
00596 {
00597 var keys = [aDictionary allKeys],
00598 count = [keys count];
00599
00600 while (count--)
00601 [_rangeEntries[current].attributes setObject:[aDictionary objectForKey:keys[count]] forKey:keys[count]];
00602
00603 current++;
00604 }
00605
00606
00607 [self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
00608
00609 [self endEditing];
00610 }
00611
00624 - (void)addAttribute:(CPString)anAttribute value:(id)aValue range:(CPRange)aRange
00625 {
00626 [self addAttributes:[CPDictionary dictionaryWithObject:aValue forKey:anAttribute] range:aRange];
00627 }
00628
00635 - (void)removeAttribute:(CPString)anAttribute range:(CPRange)aRange
00636 {
00637 [self addAttribute:anAttribute value:nil range:aRange];
00638 }
00639
00640
00646 - (void)appendAttributedString:(CPAttributedString)aString
00647 {
00648 [self insertAttributedString:aString atIndex:_string.length];
00649 }
00650
00660 - (void)insertAttributedString:(CPAttributedString)aString atIndex:(unsigned)anIndex
00661 {
00662 [self beginEditing];
00663
00664 if (anIndex < 0 || anIndex > [self length])
00665 [CPException raise:CPRangeException reason:"tried to insert attributed string at an invalid index: "+anIndex];
00666
00667 var entryIndexOfNextEntry = [self _indexOfRangeEntryForIndex:anIndex splitOnMaxIndex:YES],
00668 otherRangeEntries = aString._rangeEntries,
00669 length = [aString length];
00670
00671 if (entryIndexOfNextEntry == CPNotFound)
00672 entryIndexOfNextEntry = _rangeEntries.length;
00673
00674 _string = _string.substring(0, anIndex) + aString._string + _string.substring(anIndex);
00675
00676 var current = entryIndexOfNextEntry;
00677 while (current < _rangeEntries.length)
00678 _rangeEntries[current++].range.location += length;
00679
00680 var newRangeEntryCount = otherRangeEntries.length,
00681 index = 0;
00682
00683 while (index < newRangeEntryCount)
00684 {
00685 var entryCopy = copyRangeEntry(otherRangeEntries[index++]);
00686 entryCopy.range.location += anIndex;
00687
00688 _rangeEntries.splice(entryIndexOfNextEntry-1+index, 0, entryCopy);
00689 }
00690
00691
00692
00693
00694 [self endEditing];
00695 }
00696
00705 - (void)replaceCharactersInRange:(CPRange)aRange withAttributedString:(CPAttributedString)aString
00706 {
00707 [self beginEditing];
00708
00709 [self deleteCharactersInRange:aRange];
00710 [self insertAttributedString:aString atIndex:aRange.location];
00711
00712 [self endEditing];
00713 }
00714
00720 - (void)setAttributedString:(CPAttributedString)aString
00721 {
00722 [self beginEditing];
00723
00724 _string = aString._string;
00725 _rangeEntries = [];
00726
00727 for (var i=0, count = aString._rangeEntries.length; i<count; i++)
00728 _rangeEntries.push(copyRangeEntry(aString._rangeEntries[i]));
00729
00730 [self endEditing];
00731 }
00732
00733
00734 - (void)_indexOfRangeEntryForIndex:(unsigned)characterIndex splitOnMaxIndex:(BOOL)split
00735 {
00736 var index = [self _indexOfEntryWithIndex:characterIndex];
00737
00738 if (index < 0)
00739 return index;
00740
00741 var rangeEntry = _rangeEntries[index];
00742
00743 if (rangeEntry.range.location == characterIndex || (CPMaxRange(rangeEntry.range) - 1 == characterIndex && !split))
00744 return index;
00745
00746 var newEntries = splitRangeEntryAtIndex(rangeEntry, characterIndex);
00747 _rangeEntries.splice(index, 1, newEntries[0], newEntries[1]);
00748 index++;
00749
00750 return index;
00751 }
00752
00753 - (void)_coalesceRangeEntriesFromIndex:(unsigned)start toIndex:(unsigned)end
00754 {
00755 var current = start;
00756
00757 if (end >= _rangeEntries.length)
00758 end = _rangeEntries.length -1;
00759
00760 while (current < end)
00761 {
00762 var a = _rangeEntries[current],
00763 b = _rangeEntries[current+1];
00764
00765 if ([a.attributes isEqualToDictionary:b.attributes])
00766 {
00767 a.range.length = CPMaxRange(b.range) - a.range.location;
00768 _rangeEntries.splice(current+1, 1);
00769 end--;
00770 }
00771 else
00772 current++;
00773 }
00774 }
00775
00776
00781 - (void)beginEditing
00782 {
00783
00784 }
00785
00790 - (void)endEditing
00791 {
00792
00793 }
00794
00795 @end
00796
00797 @implementation CPMutableAttributedString : CPAttributedString {}
00798 @end
00799
00800 var isEqual = function isEqual(a, b)
00801 {
00802 if (a == b)
00803 return YES;
00804
00805 if ([a respondsToSelector:@selector(isEqual:)] && [a isEqual:b])
00806 return YES;
00807
00808 return NO;
00809 }
00810
00811 var makeRangeEntry = function makeRangeEntry(aRange, attributes)
00812 {
00813 return {range:aRange, attributes:[attributes copy]};
00814 }
00815
00816 var copyRangeEntry = function copyRangeEntry(aRangeEntry)
00817 {
00818 return makeRangeEntry(CPCopyRange(aRangeEntry.range), [aRangeEntry.attributes copy]);
00819 }
00820
00821 var splitRangeEntry = function splitRangeEntryAtIndex(aRangeEntry, anIndex)
00822 {
00823 var newRangeEntry = copyRangeEntry(aRangeEntry),
00824 cachedIndex = CPMaxRange(aRangeEntry.range);
00825
00826 aRangeEntry.range.length = anIndex - aRangeEntry.range.location;
00827 newRangeEntry.range.location = anIndex;
00828 newRangeEntry.range.length = cachedIndex - anIndex;
00829 newRangeEntry.attributes = [newRangeEntry.attributes copy];
00830
00831 return [aRangeEntry, newRangeEntry];
00832 }