00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import <Foundation/CPObject.j>
00024 @import <Foundation/CPString.j>
00025
00026 @implementation CPAttributedString : CPObject
00027 {
00028 CPString _string;
00029 CPArray _rangeEntries;
00030 }
00031
00032
00033 - (id)initWithString:(CPString)aString
00034 {
00035 return [self initWithString:aString attributes:nil];
00036 }
00037
00038 - (id)initWithAttributedString:(CPAttributedString)aString
00039 {
00040 var string = [self initWithString:"" attributes:nil];
00041
00042 [string setAttributedString:aString];
00043
00044 return string;
00045 }
00046
00047 - (id)initWithString:(CPString)aString attributes:(CPDictionary)attributes
00048 {
00049 self = [super init];
00050
00051 if (!attributes)
00052 attributes = [CPDictionary dictionary];
00053
00054 _string = ""+aString;
00055 _rangeEntries = [makeRangeEntry(CPMakeRange(0, _string.length), attributes)];
00056
00057 return self;
00058 }
00059
00060
00061 - (CPString)string
00062 {
00063 return _string;
00064 }
00065
00066 - (CPString)mutableString
00067 {
00068 return [self string];
00069 }
00070
00071 - (unsigned)length
00072 {
00073 return _string.length;
00074 }
00075
00076 - (unsigned)_indexOfEntryWithIndex:(unsigned)anIndex
00077 {
00078 if (anIndex < 0 || anIndex > _string.length || anIndex === undefined)
00079 return CPNotFound;
00080
00081
00082 var sortFunction = function(index, entry)
00083 {
00084
00085 if (CPLocationInRange(index, entry.range))
00086 return CPOrderedSame;
00087 else if (CPMaxRange(entry.range) <= index)
00088 return CPOrderedDescending;
00089 else
00090 return CPOrderedAscending;
00091 }
00092
00093 return [_rangeEntries indexOfObject:anIndex sortedByFunction:sortFunction];
00094 }
00095
00096
00097 - (CPDictionary)attributesAtIndex:(unsigned)anIndex effectiveRange:(CPRangePointer)aRange
00098 {
00099
00100 var entryIndex = [self _indexOfEntryWithIndex:anIndex];
00101
00102 if (entryIndex == CPNotFound)
00103 return nil;
00104
00105 var matchingRange = _rangeEntries[entryIndex];
00106 if (aRange)
00107 {
00108 aRange.location = matchingRange.range.location;
00109 aRange.length = matchingRange.range.length;
00110 }
00111
00112 return matchingRange.attributes;
00113 }
00114
00115 - (CPDictionary)attributesAtIndex:(unsigned)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
00116 {
00117 var startingEntryIndex = [self _indexOfEntryWithIndex:anIndex];
00118
00119 if (startingEntryIndex == CPNotFound)
00120 return nil;
00121
00122 if (!aRange)
00123 return _rangeEntries[startingEntryIndex].attributes;
00124
00125 if (CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
00126 {
00127 aRange.location = rangeLimit.location;
00128 aRange.length = rangeLimit.length;
00129
00130 return _rangeEntries[startingEntryIndex].attributes;
00131 }
00132
00133
00134 var nextRangeIndex = startingEntryIndex - 1,
00135 currentEntry = _rangeEntries[startingEntryIndex],
00136 comparisonDict = currentEntry.attributes;
00137
00138 while (nextRangeIndex >= 0)
00139 {
00140 var nextEntry = _rangeEntries[nextRangeIndex];
00141
00142 if (CPMaxRange(nextEntry.range) > rangeLimit.location && [nextEntry.attributes isEqualToDictionary:comparisonDict])
00143 {
00144 currentEntry = nextEntry;
00145 nextRangeIndex--;
00146 }
00147 else
00148 break;
00149 }
00150
00151 aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
00152
00153
00154 currentEntry = _rangeEntries[startingEntryIndex];
00155 nextRangeIndex = startingEntryIndex + 1;
00156
00157 while (nextRangeIndex < _rangeEntries.length)
00158 {
00159 var nextEntry = _rangeEntries[nextRangeIndex];
00160
00161 if (nextEntry.range.location < CPMaxRange(rangeLimit) && [nextEntry.attributes isEqualToDictionary:comparisonDict])
00162 {
00163 currentEntry = nextEntry;
00164 nextRangeIndex++;
00165 }
00166 else
00167 break;
00168 }
00169
00170 aRange.length = MIN(CPMaxRange(currentEntry.range), CPMaxRange(rangeLimit)) - aRange.location;
00171
00172 return comparisonDict;
00173 }
00174
00175 - (id)attribute:(CPString)attribute atIndex:(unsigned)index effectiveRange:(CPRangePointer)aRange
00176 {
00177 if (!attribute)
00178 {
00179 if (aRange)
00180 {
00181 aRange.location = 0;
00182 aRange.length = _string.length;
00183 }
00184
00185 return nil;
00186 }
00187
00188 return [[self attributesAtIndex:index effectiveRange:aRange] valueForKey:attribute];
00189 }
00190
00191 - (id)attribute:(CPString)attribute atIndex:(unsigned)anIndex longestEffectiveRange:(CPRangePointer)aRange inRange:(CPRange)rangeLimit
00192 {
00193
00194 var startingEntryIndex = [self _indexOfEntryWithIndex:anIndex];
00195
00196 if (startingEntryIndex == CPNotFound || !attribute)
00197 return nil;
00198
00199 if (!aRange)
00200 return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
00201
00202 if (CPRangeInRange(_rangeEntries[startingEntryIndex].range, rangeLimit))
00203 {
00204 aRange.location = rangeLimit.location;
00205 aRange.length = rangeLimit.length;
00206
00207 return [_rangeEntries[startingEntryIndex].attributes objectForKey:attribute];
00208 }
00209
00210
00211 var nextRangeIndex = startingEntryIndex - 1,
00212 currentEntry = _rangeEntries[startingEntryIndex],
00213 comparisonAttribute = [currentEntry.attributes objectForKey:attribute];
00214
00215 while (nextRangeIndex >= 0)
00216 {
00217 var nextEntry = _rangeEntries[nextRangeIndex];
00218
00219 if (CPMaxRange(nextEntry.range) > rangeLimit.location && isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
00220 {
00221 currentEntry = nextEntry;
00222 nextRangeIndex--;
00223 }
00224 else
00225 break;
00226 }
00227
00228 aRange.location = MAX(currentEntry.range.location, rangeLimit.location);
00229
00230
00231 currentEntry = _rangeEntries[startingEntryIndex];
00232 nextRangeIndex = startingEntryIndex + 1;
00233
00234 while (nextRangeIndex < _rangeEntries.length)
00235 {
00236 var nextEntry = _rangeEntries[nextRangeIndex];
00237
00238 if (nextEntry.range.location < CPMaxRange(rangeLimit) && isEqual(comparisonAttribute, [nextEntry.attributes objectForKey:attribute]))
00239 {
00240 currentEntry = nextEntry;
00241 nextRangeIndex++;
00242 }
00243 else
00244 break;
00245 }
00246
00247 aRange.length = MIN(CPMaxRange(currentEntry.range), CPMaxRange(rangeLimit)) - aRange.location;
00248
00249 return comparisonAttribute;
00250 }
00251
00252
00253 - (BOOL)isEqualToAttributedString:(CPAttributedString)aString
00254 {
00255 if(!aString)
00256 return NO;
00257
00258 if(_string != [aString string])
00259 return NO;
00260
00261 var myRange = CPMakeRange(),
00262 comparisonRange = CPMakeRange(),
00263 myAttributes = [self attributesAtIndex:0 effectiveRange:myRange],
00264 comparisonAttributes = [aString attributesAtIndex:0 effectiveRange:comparisonRange],
00265 length = _string.length;
00266
00267 while (CPMaxRange(CPUnionRange(myRange, comparisonRange)) < length)
00268 {
00269 if (CPIntersectionRange(myRange, comparisonRange).length > 0 && ![myAttributes isEqualToDictionary:comparisonAttributes])
00270 return NO;
00271 if (CPMaxRange(myRange) < CPMaxRange(comparisonRange))
00272 myAttributes = [self attributesAtIndex:CPMaxRange(myRange) effectiveRange:myRange];
00273 else
00274 comparisonAttributes = [aString attributesAtIndex:CPMaxRange(comparisonRange) effectiveRange:comparisonRange];
00275 }
00276
00277 return YES;
00278 }
00279
00280 - (BOOL)isEqual:(id)anObject
00281 {
00282 if (anObject == self)
00283 return YES;
00284
00285 if ([anObject isKindOfClass:[self class]])
00286 return [self isEqualToAttributedString:anObject];
00287
00288 return NO;
00289 }
00290
00291
00292 - (CPAttributedString)attributedSubstringFromRange:(CPRange)aRange
00293 {
00294 if (!aRange || CPMaxRange(aRange) > _string.length || aRange.location < 0)
00295 [CPException raise:CPRangeException
00296 reason:"tried to get attributedSubstring for an invalid range: "+(aRange?CPStringFromRange(aRange):"nil")];
00297
00298 var newString = [[CPAttributedString alloc] initWithString:_string.substring(aRange.location, CPMaxRange(aRange))],
00299 entryIndex = [self _indexOfEntryWithIndex:aRange.location],
00300 currentRangeEntry = _rangeEntries[entryIndex],
00301 lastIndex = CPMaxRange(aRange);
00302
00303 newString._rangeEntries = [];
00304
00305 while (currentRangeEntry && CPMaxRange(currentRangeEntry.range) < lastIndex)
00306 {
00307 var newEntry = copyRangeEntry(currentRangeEntry);
00308 newEntry.range.location -= aRange.location;
00309
00310 if (newEntry.range.location < 0)
00311 {
00312 newEntry.range.length += newEntry.range.location;
00313 newEntry.range.location = 0;
00314 }
00315
00316 newString._rangeEntries.push(newEntry);
00317 currentRangeEntry = _rangeEntries[++entryIndex];
00318 }
00319
00320 if (currentRangeEntry)
00321 {
00322 var newRangeEntry = copyRangeEntry(currentRangeEntry);
00323
00324 newRangeEntry.range.length = CPMaxRange(aRange) - newRangeEntry.range.location;
00325 newRangeEntry.range.location -= aRange.location;
00326
00327 if (newRangeEntry.range.location < 0)
00328 {
00329 newRangeEntry.range.length += newRangeEntry.range.location;
00330 newRangeEntry.range.location = 0;
00331 }
00332
00333 newString._rangeEntries.push(newRangeEntry);
00334 }
00335
00336 return newString;
00337 }
00338
00339
00340 - (void)replaceCharactersInRange:(CPRange)aRange withString:(CPString)aString
00341 {
00342 [self beginEditing];
00343
00344 if (!aString)
00345 aString = "";
00346
00347 var startingIndex = [self _indexOfEntryWithIndex:aRange.location],
00348 startingRangeEntry = _rangeEntries[startingIndex],
00349 endingIndex = [self _indexOfEntryWithIndex:CPMaxRange(aRange)-1],
00350 endingRangeEntry = _rangeEntries[endingIndex],
00351 additionalLength = aString.length - aRange.length;
00352
00353 _string = _string.substring(0, aRange.location) + aString + _string.substring(CPMaxRange(aRange));
00354
00355 if (startingIndex == endingIndex)
00356 startingRangeEntry.range.length += additionalLength;
00357 else
00358 {
00359 endingRangeEntry.range.length = CPMaxRange(endingRangeEntry.range) - CPMaxRange(aRange);
00360 endingRangeEntry.range.location = CPMaxRange(aRange);
00361
00362 startingRangeEntry.range.length = CPMaxRange(aRange) - startingRangeEntry.range.location;
00363
00364 _rangeEntries.splice(startingIndex, endingIndex - startingIndex);
00365 }
00366
00367 endingIndex = startingIndex + 1;
00368
00369 while(endingIndex < _rangeEntries.length)
00370 _rangeEntries[endingIndex++].range.location+=additionalLength;
00371
00372 [self endEditing];
00373 }
00374
00375 - (void)deleteCharactersInRange:(CPRange)aRange
00376 {
00377 [self replaceCharactersInRange:aRange withString:nil];
00378 }
00379
00380
00381 - (void)setAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
00382 {
00383 [self beginEditing];
00384
00385 var startingEntryIndex = [self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
00386 endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
00387 current = startingEntryIndex;
00388
00389 if (endingEntryIndex == CPNotFound)
00390 endingEntryIndex = _rangeEntries.length;
00391
00392 while (current < endingEntryIndex)
00393 _rangeEntries[current++].attributes = [aDictionary copy];
00394
00395
00396 [self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
00397
00398 [self endEditing];
00399 }
00400
00401 - (void)addAttributes:(CPDictionary)aDictionary range:(CPRange)aRange
00402 {
00403 [self beginEditing];
00404
00405 var startingEntryIndex = [self _indexOfRangeEntryForIndex:aRange.location splitOnMaxIndex:YES],
00406 endingEntryIndex = [self _indexOfRangeEntryForIndex:CPMaxRange(aRange) splitOnMaxIndex:YES],
00407 current = startingEntryIndex;
00408
00409 if (endingEntryIndex == CPNotFound)
00410 endingEntryIndex = _rangeEntries.length;
00411
00412 while (current < endingEntryIndex)
00413 {
00414 var keys = [aDictionary allKeys],
00415 count = [keys count];
00416
00417 while (count--)
00418 [_rangeEntries[current].attributes setObject:[aDictionary objectForKey:keys[count]] forKey:keys[count]];
00419
00420 current++;
00421 }
00422
00423
00424 [self _coalesceRangeEntriesFromIndex:startingEntryIndex toIndex:endingEntryIndex];
00425
00426 [self endEditing];
00427 }
00428
00429 - (void)addAttribute:(CPString)anAttribute value:(id)aValue range:(CPRange)aRange
00430 {
00431 [self addAttributes:[CPDictionary dictionaryWithObject:aValue forKey:anAttribute] range:aRange];
00432 }
00433
00434 - (void)removeAttribute:(CPString)anAttribute range:(CPRange)aRange
00435 {
00436 [self addAttribute:anAttribute value:nil range:aRange];
00437 }
00438
00439
00440 - (void)appendAttributedString:(CPAttributedString)aString
00441 {
00442 [self insertAttributedString:aString atIndex:_string.length];
00443 }
00444
00445 - (void)insertAttributedString:(CPAttributedString)aString atIndex:(CPString)anIndex
00446 {
00447 [self beginEditing];
00448
00449 if (anIndex < 0 || anIndex > [self length])
00450 [CPException raise:CPRangeException reason:"tried to insert attributed string at an invalid index: "+anIndex];
00451
00452 var entryIndexOfNextEntry = [self _indexOfRangeEntryForIndex:anIndex splitOnMaxIndex:YES],
00453 otherRangeEntries = aString._rangeEntries,
00454 length = [aString length];
00455
00456 if (entryIndexOfNextEntry == CPNotFound)
00457 entryIndexOfNextEntry = _rangeEntries.length;
00458
00459 _string = _string.substring(0, anIndex) + aString._string + _string.substring(anIndex);
00460
00461 var current = entryIndexOfNextEntry;
00462 while (current < _rangeEntries.length)
00463 _rangeEntries[current++].range.location += length;
00464
00465 var newRangeEntryCount = otherRangeEntries.length,
00466 index = 0;
00467
00468 while (index < newRangeEntryCount)
00469 {
00470 var entryCopy = copyRangeEntry(otherRangeEntries[index++]);
00471 entryCopy.range.location += anIndex;
00472
00473 _rangeEntries.splice(entryIndexOfNextEntry-1+index, 0, entryCopy);
00474 }
00475
00476
00477
00478
00479 [self endEditing];
00480 }
00481
00482 - (void)replaceCharactersInRange:(CPRange)aRange withAttributedString:(CPAttributedString)aString
00483 {
00484 [self beginEditing];
00485
00486 [self deleteCharactersInRange:aRange];
00487 [self insertAttributedString:aString atIndex:aRange.location];
00488
00489 [self endEditing];
00490 }
00491
00492 - (void)setAttributedString:(CPAttributedString)aString
00493 {
00494 [self beginEditing];
00495
00496 _string = aString._string;
00497 _rangeEntries = [];
00498
00499 for (var i=0, count = aString._rangeEntries.length; i<count; i++)
00500 _rangeEntries.push(copyRangeEntry(aString._rangeEntries[i]));
00501
00502 [self endEditing];
00503 }
00504
00505
00506 - (void)_indexOfRangeEntryForIndex:(unsigned)characterIndex splitOnMaxIndex:(BOOL)split
00507 {
00508 var index = [self _indexOfEntryWithIndex:characterIndex];
00509
00510 if (index < 0)
00511 return index;
00512
00513 var rangeEntry = _rangeEntries[index];
00514
00515 if (rangeEntry.range.location == characterIndex || (CPMaxRange(rangeEntry.range) - 1 == characterIndex && !split))
00516 return index;
00517
00518 var newEntries = splitRangeEntryAtIndex(rangeEntry, characterIndex);
00519 _rangeEntries.splice(index, 1, newEntries[0], newEntries[1]);
00520 index++;
00521
00522 return index;
00523 }
00524
00525 - (void)_coalesceRangeEntriesFromIndex:(unsigned)start toIndex:(unsigned)end
00526 {
00527 var current = start;
00528
00529 if (end >= _rangeEntries.length)
00530 end = _rangeEntries.length -1;
00531
00532 while (current < end)
00533 {
00534 var a = _rangeEntries[current],
00535 b = _rangeEntries[current+1];
00536
00537 if ([a.attributes isEqualToDictionary:b.attributes])
00538 {
00539 a.range.length = CPMaxRange(b.range) - a.range.location;
00540 _rangeEntries.splice(current+1, 1);
00541 end--;
00542 }
00543 else
00544 current++;
00545 }
00546 }
00547
00548
00549 - (void)beginEditing
00550 {
00551
00552 }
00553
00554 - (void)endEditing
00555 {
00556
00557 }
00558
00559 @end
00560
00561 @implementation CPMutableAttributedString : CPAttributedString {}
00562 @end
00563
00564 var isEqual = function isEqual(a, b)
00565 {
00566 if (a == b)
00567 return YES;
00568
00569 if ([a respondsToSelector:@selector(isEqual:)] && [a isEqual:b])
00570 return YES;
00571
00572 return NO;
00573 }
00574
00575 var makeRangeEntry = function makeRangeEntry(aRange, attributes)
00576 {
00577 return {range:aRange, attributes:[attributes copy]};
00578 }
00579
00580 var copyRangeEntry = function copyRangeEntry(aRangeEntry)
00581 {
00582 return makeRangeEntry(CPCopyRange(aRangeEntry.range), [aRangeEntry.attributes copy]);
00583 }
00584
00585 var splitRangeEntry = function splitRangeEntryAtIndex(aRangeEntry, anIndex)
00586 {
00587 var newRangeEntry = copyRangeEntry(aRangeEntry),
00588 cachedIndex = CPMaxRange(aRangeEntry.range);
00589
00590 aRangeEntry.range.length = anIndex - aRangeEntry.range.location;
00591 newRangeEntry.range.location = anIndex;
00592 newRangeEntry.range.length = cachedIndex - anIndex;
00593 newRangeEntry.attributes = [newRangeEntry.attributes copy];
00594
00595 return [aRangeEntry, newRangeEntry];
00596 }