/* NSAttributedString.m Implementation of string class with attributes Copyright (C) 1997 Free Software Foundation, Inc. Author: ANOQ of the sun Date: November 1997 This file is part of the mySTEP Library and is provided under the terms of the GNU Library General Public License. */ //FIXME: 1) The NSMutableString object returned from the -mutableString method // in NSMutableAttributedString is NOT tracked for changes to update // NSMutableAttributedString's attributes as it should. // ranges are defined as 0..length-1 according to NSRange #import #import #import #import #import #import #import #import #import "NSPrivate.h" static Class __attrStrClass; static Class __mutableAttrStrClass; void _setAttributesFrom( NSAttributedString *attributedString, NSRange aRange, NSMutableArray *attributeArray, NSMutableArray *locateArray) { // always called immediately after -initWithString:attributes: or by setAttributedString: NSRange effectiveRange; NSDictionary *attributeDict; unsigned m; if(aRange.length == 0) return; // No attributes attributeDict = [attributedString attributesAtIndex:aRange.location effectiveRange:&effectiveRange]; [attributeArray replaceObjectAtIndex:0 withObject:attributeDict]; while ((m = NSMaxRange(effectiveRange)) < NSMaxRange(aRange)) { attributeDict = [attributedString attributesAtIndex:m effectiveRange:&effectiveRange]; [attributeArray addObject:attributeDict]; [locateArray addObject: [NSNumber numberWithUnsignedInt: effectiveRange.location - aRange.location]]; } #if 0 NSLog(@"_setAttributesFrom -> %@", locateArray); #endif } NSDictionary * _attributesAtIndexEffectiveRange(unsigned int index, NSRange *aRange, // may be NULL unsigned int strLength, NSMutableArray *attributeArray, NSMutableArray *locateArray, unsigned int *foundIndex) { // locate by binary search for efficiency in huge attributed strings int low, high, cnt; unsigned int used, foundLoc, nextLoc; NSDictionary *foundDict; #if 0 NSLog(@"_attributesAtIndexEffectiveRange %d, len=%d", index, strLength); #endif if(strLength > 0 && index >= strLength) [NSException raise:NSRangeException format: @"index out of range in \ _attributesAtIndexEffectiveRange(%d, %@, %d, ...)", index, aRange?NSStringFromRange(*aRange):(NSString *)@"NULL", strLength]; used = [attributeArray count]; low = 0; high = used - 1; while(low <= high) { cnt = (low + high) / 2; #if 0 NSLog(@"low %d cnt %d high %d", low, cnt, high); #endif foundDict = [attributeArray objectAtIndex:cnt]; foundLoc = [[locateArray objectAtIndex:cnt] unsignedIntValue]; if(foundLoc > index) high = cnt-1; else { if(cnt >= used - 1) nextLoc = strLength; // applies to all characters up to end of string else { nextLoc = [[locateArray objectAtIndex:cnt+1] unsignedIntValue]; NSCAssert1(nextLoc > foundLoc, @"locateArray must be ascending (%@)", locateArray); } if(foundLoc == index || index < nextLoc) { // found if(aRange) { aRange->location = foundLoc; aRange->length = nextLoc - foundLoc; } if(foundIndex) *foundIndex = cnt; #if 0 NSLog(@"found %@ at %d", NSStringFromRange(NSMakeRange(foundLoc, nextLoc-foundLoc)), cnt); #endif return foundDict; } else low = cnt+1; } } NSCAssert(NO, @"Error in binary search algorithm"); return nil; } @interface _NSMutableAttributedStringProxy : NSMutableString { // returned by [astring mutableString] NSMutableAttributedString *_astring; // original } - (id) initWithAttributedString:(NSAttributedString *) astring; @end @implementation _NSMutableAttributedStringProxy // this is a subclass of NSMutableString - (id) initWithAttributedString:(NSAttributedString *) astring; { // we don't call super init! _astring=(NSMutableAttributedString *) [astring retain]; return self; } - (id) copyWithZone:(NSZone *) zone { return [[_astring string] retain]; // convert to "normal" NSString } - (void) dealloc { [_astring release]; [super dealloc]; // this is NSMutableString's dealloc } // FIXME: make all changes update attributes in _astring // i.e. setString // etc. - (void) setString:(NSString *) str { [_astring replaceCharactersInRange:NSMakeRange(0, [_astring length]) withString:str]; // retains attributes of first character } /// implement other wrapper methods that can't be inherited from NSMutableString @end @implementation NSAttributedString + (void) initialize { if (self == [NSAttributedString class]) { __attrStrClass = [NSAttributedString class]; __mutableAttrStrClass = [NSMutableAttributedString class]; } } - (id) copyWithZone:(NSZone *) zone // NSCopying protocol { if ([self isKindOfClass: [NSMutableAttributedString class]]) return [[NSAttributedString alloc] initWithAttributedString:self]; return [self retain]; } // NSMutableCopying protocol - (id) mutableCopyWithZone:(NSZone *) zone { return [[NSMutableAttributedString alloc] initWithAttributedString:self]; } - (id) init { return [self initWithString:nil attributes:nil]; } - (id) initWithString:(NSString *)aString { return [self initWithString:aString attributes:nil]; } - (id) initWithAttributedString:(NSAttributedString *)attributedString { if((self=[self initWithString:[attributedString string] attributes:nil])) { _setAttributesFrom(attributedString, NSMakeRange(0, [attributedString length]), _attributes, _locations); } return self; } - (id) initWithString:(NSString *)aString attributes:(NSDictionary *)attributes { if((self=[super init])) { _string = [aString copy]; _attributes = [[NSMutableArray alloc] initWithCapacity:3]; _locations = [[NSMutableArray alloc] initWithCapacity:3]; if(!attributes) attributes = [NSDictionary dictionary]; [_attributes addObject:attributes]; [_locations addObject:[NSNumber numberWithUnsignedInt:0]]; } return self; } - (NSString *) description { return [self string]; } - (void) dealloc { [_string release]; [_attributes release]; [_locations release]; [super dealloc]; } - (unsigned int) length { return [_string length]; } - (NSString *) string { return _string; } - (NSDictionary *) attributesAtIndex:(unsigned int)index effectiveRange:(NSRange *)aRange { return _attributesAtIndexEffectiveRange( index, aRange, [self length], _attributes, _locations, NULL); } - (NSDictionary *) attributesAtIndex:(unsigned int)index longestEffectiveRange:(NSRange *)aRange inRange:(NSRange)rangeLimit { NSDictionary *attrDictionary; unsigned idx, i, cnt; if(NSMaxRange(rangeLimit) > [self length]) [NSException raise:NSRangeException format:@"RangeError in -attributesAtIndex:%d longestEffectiveRange:%@ inRange:%@", index, aRange?NSStringFromRange(*aRange):(NSString *) @"NULL", NSStringFromRange(rangeLimit)]; attrDictionary=_attributesAtIndexEffectiveRange(index, aRange, [self length], _attributes, _locations, &idx); if(!aRange) return attrDictionary; for(i=idx; i > 0; i--) { if([[_locations objectAtIndex:i] unsignedIntValue] < rangeLimit.location) break; // check if we look before range limit - then we can stop comparing full dictionaries if(![[_attributes objectAtIndex:i-1] isEqualToDictionary:attrDictionary]) break; // get first one that still has same dict value } for(cnt=[_attributes count], idx++; idx < cnt; idx++) { if([[_locations objectAtIndex:idx] unsignedIntValue] > NSMaxRange(rangeLimit)) break; // check if we look behind range limit - then we can stop comparing full dictionaries if(![[_attributes objectAtIndex:idx] isEqualToDictionary:attrDictionary]) break; // get first one that no longer has same dict value } aRange->location=[[_locations objectAtIndex:i] unsignedIntValue]; // first if(idx == cnt) aRange->length=[self length]-aRange->location; // remainder else aRange->length=[[_locations objectAtIndex:idx] unsignedIntValue]-aRange->location; // last *aRange = NSIntersectionRange(*aRange, rangeLimit); // clip to rangeLimit return attrDictionary; } - (id) attribute:(NSString *)attributeName atIndex:(unsigned int)index effectiveRange:(NSRange *)aRange { NSDictionary *tmpDictionary; tmpDictionary = [self attributesAtIndex:index effectiveRange:aRange]; // Raises exception if index is out of range if(!attributeName) { if(aRange) *aRange = NSMakeRange(0, [self length]); // If attributeName is nil, then the attribute will not exist in the // entire text - therefore aRange of the entire text must be correct return nil; } return [tmpDictionary objectForKey:attributeName]; } - (id) attribute:(NSString *)attributeName atIndex:(unsigned int)index longestEffectiveRange:(NSRange *)aRange inRange:(NSRange)rangeLimit { NSDictionary *tmpDictionary; id attrValue, tmpAttrValue; NSRange tmpRange; if(NSMaxRange(rangeLimit) > [self length]) [NSException raise:NSRangeException format:@"RangeError in -attribute:%@ atIndex:%d longestEffectiveRange:%@ inRange:%@", attributeName, index, NSStringFromRange(*aRange), NSStringFromRange(rangeLimit)]; attrValue = [self attribute:attributeName atIndex:index effectiveRange:aRange]; // Raises exception if index is out of range if(!attributeName) return nil; // attribute:atIndex:effectiveRange: handles this case. if(!aRange) return attrValue; while(aRange->location > rangeLimit.location) { // Check extend range backwards tmpDictionary = [self attributesAtIndex:aRange->location - 1 effectiveRange:&tmpRange]; tmpAttrValue = [tmpDictionary objectForKey:attributeName]; if(tmpAttrValue == attrValue) aRange->location = tmpRange.location; } while(NSMaxRange(*aRange) < NSMaxRange(rangeLimit)) { // Check extend range forwards tmpDictionary = [self attributesAtIndex:NSMaxRange(*aRange) effectiveRange:&tmpRange]; tmpAttrValue = [tmpDictionary objectForKey:attributeName]; if(tmpAttrValue == attrValue) aRange->length = NSMaxRange(tmpRange) - aRange->location; } *aRange = NSIntersectionRange(*aRange,rangeLimit); // Clip to rangeLimit return attrValue; } // Comparing attributed strings - (BOOL) isEqualToAttributedString:(NSAttributedString *)otherString { NSRange ownEffectiveRange,otherEffectiveRange; unsigned int length; NSDictionary *ownDictionary,*otherDictionary; BOOL result; if(!otherString) return NO; if(![[otherString string] isEqual:[self string]]) return NO; length = [otherString length]; if(length <= 0) return YES; ownDictionary = [self attributesAtIndex:0 effectiveRange:&ownEffectiveRange]; otherDictionary = [otherString attributesAtIndex:0 effectiveRange:&otherEffectiveRange]; result = YES; while(YES) { if(NSIntersectionRange(ownEffectiveRange,otherEffectiveRange).length >0 && ![ownDictionary isEqualToDictionary:otherDictionary]) { result = NO; break; } if(NSMaxRange(ownEffectiveRange) < NSMaxRange(otherEffectiveRange)) { ownDictionary = [self attributesAtIndex:NSMaxRange(ownEffectiveRange) effectiveRange:&ownEffectiveRange]; } else { if(NSMaxRange(otherEffectiveRange) >= length) break; // End of strings otherDictionary = [otherString attributesAtIndex: NSMaxRange(otherEffectiveRange) effectiveRange:&otherEffectiveRange]; } } return result; } - (BOOL) isEqual:(id)anObject { if (anObject == self) return YES; if ([anObject isKindOfClass:[NSAttributedString class]]) return [self isEqualToAttributedString:anObject]; return NO; } - (NSAttributedString *) attributedSubstringFromRange:(NSRange)aRange { NSAttributedString *newAttrString; // Extract a substring if(NSMaxRange(aRange) > [self length]) [NSException raise:NSRangeException format:@"RangeError in -attributedSubstringFromRange:%@", NSStringFromRange(aRange)]; newAttrString = [[[NSAttributedString alloc] initWithString:[_string substringWithRange:aRange] attributes:nil] autorelease]; _setAttributesFrom(newAttrString, aRange, ((NSAttributedString *)newAttrString)->_attributes, ((NSAttributedString *)newAttrString)->_locations); return newAttrString; } - (void) encodeWithCoder:(NSCoder *)aCoder // NSCoding protocol { [super encodeWithCoder:aCoder]; [aCoder encodeObject:_string]; [aCoder encodeObject:_attributes]; [aCoder encodeObject:_locations]; } - (id) initWithCoder:(NSCoder *)aCoder { #if 0 NSLog(@"%@ initWithCoder: %@", NSStringFromClass([self class]), coder); NSLog(@"NSAttributes=%@", [coder decodeObjectForKey:@"NSAttributes"]); NSLog(@"NSString=%@", [coder decodeObjectForKey:@"NSString"]); NSLog(@"NSAttributeInfo=%@", [coder decodeObjectForKey:@"NSAttributeInfo"]); // NSData(!) NSLog(@"NSDelegate=%@", [coder decodeObjectForKey:@"NSDelegate"]); // NSTextStorage only #endif self = [super initWithCoder:aCoder]; if([aCoder allowsKeyedCoding]) { if([aCoder containsValueForKey:@"NSAttributeInfo"]) { // we have several attribute runs and NSAttributes is an Array of the attributes const unsigned char *p, *end; unsigned int pos; NSArray *attribs=[aCoder decodeObjectForKey:@"NSAttributes"]; // array of unique attributes /* format of the info 1 byte length of the run; if >=128 this are the low order 7 bits and the next bytes defines 8 (or 7?) more bits 1 byte index of into the attributes array */ NSData *info=[aCoder decodeObjectForKey:@"NSAttributeInfo"]; self=[self initWithString:[aCoder decodeObjectForKey:@"NSString"] attributes:nil]; p=[info bytes]; end=p+[info length]; pos=0; [_locations removeAllObjects]; // will be replaced [_attributes removeAllObjects]; // will be replaced while(p < end) { // process encoded runs unsigned len, idx; len=*p++; if(len >= 128) { // handle multibyte value // NSLog(@"len = %d [%d] %@", len, [self length], info); // unknown: format for strings longer than 16k (i.e. if second byte is also >= 128) len=(len-128)+128*(*p++); // next byte is MSB // NSLog(@"=> len=%d", len); } idx=*p++; if(idx >= 128) { // handle multibyte value // NSLog(@"idx = %d [%d] %@", len, [self length], info); // unknown: does this happen at all for more than 128 different attributes? // to test this we must create an attrib string where every second character has a different attribute set... } [_attributes addObject:[attribs objectAtIndex:idx]]; [_locations addObject:[NSNumber numberWithUnsignedInt:pos]]; pos+=len; } #if 0 NSLog(@"initWithCoder -> %@", _locations); #endif return self; } else return [self initWithString:[aCoder decodeObjectForKey:@"NSString"] attributes:[aCoder decodeObjectForKey:@"NSAttributes"]]; // single attribute run } [aCoder decodeValueOfObjCType: @encode(id) at: &_string]; [aCoder decodeValueOfObjCType: @encode(id) at: &_attributes]; [aCoder decodeValueOfObjCType: @encode(id) at: &_locations]; return self; } - (Class) classForPortCoder { return [self class]; } - (id) replacementObjectForPortCoder:(NSPortCoder*)coder { // default is to encode a proxy if([coder isBycopy]) return self; return [super replacementObjectForPortCoder:coder]; } @end /* NSAttributedString */ //***************************************************************************** // // NSMutableAttributedString // //***************************************************************************** @implementation NSMutableAttributedString - (id) initWithString:(NSString *)aString attributes:(NSDictionary *)attributes { if((self=[super initWithString:nil attributes:attributes])) { _string = [aString mutableCopy]; } return self; } - (NSAttributedString *) attributedSubstringFromRange:(NSRange)aRange { NSAttributedString *newAttrString; // Extract a substring NSString *newSubstring; if(NSMaxRange(aRange) > [self length]) [NSException raise:NSRangeException format:@"RangeError in -attributedSubstringFromRange:%@", NSStringFromRange(aRange)]; newSubstring = [[self string] substringWithRange:aRange]; newAttrString = [[NSAttributedString alloc] initWithString:newSubstring attributes:nil]; [newAttrString autorelease]; _setAttributesFrom(self, aRange, ((NSMutableAttributedString *)newAttrString)->_attributes, ((NSMutableAttributedString *)newAttrString)->_locations); return newAttrString; } - (NSMutableString *) mutableString { return [[[_NSMutableAttributedStringProxy alloc] initWithAttributedString:self] autorelease]; } - (void) beginEditing { return; } - (void) endEditing { return; } - (void) deleteCharactersInRange:(NSRange)aRange { [self replaceCharactersInRange:aRange withString:nil]; } /* Primitive method! Sets attributes and values for a given range of characters, replacing any previous attributes and values for that range. Sets the attributes for the characters in aRange to attributes. These new attributes replace any attributes previously associated with the characters aRange. Raises an NSRangeException if any part of aRange lies beyond the end of the receiver's characters. See also: - addAtributes:range:, - removeAttributes:range: */ - (void) setAttributes:(NSDictionary *)attributes range:(NSRange)range { unsigned int tmpLength, arrayIndex, arraySize, location; NSRange effectiveRange; NSNumber *afterRangeLocation, *beginRangeLocation; NSDictionary *attrs; #if 0 NSLog(@"setAttributes:%p range:%@", attributes, NSStringFromRange(range)); #endif if(!attributes) attributes = [NSDictionary dictionary]; tmpLength = [self length]; if(NSMaxRange(range) > tmpLength) [NSException raise:NSRangeException format:@"Range Error in setAttributes:... range:%@ larger than {0, %u}", NSStringFromRange(range), tmpLength]; arraySize = [_locations count]; if(NSMaxRange(range) < tmpLength) { attrs = _attributesAtIndexEffectiveRange( NSMaxRange(range), &effectiveRange,tmpLength,_attributes,_locations,&arrayIndex); afterRangeLocation = [NSNumber numberWithUnsignedInt:NSMaxRange(range)]; if(effectiveRange.location > range.location) [_locations replaceObjectAtIndex:arrayIndex withObject:afterRangeLocation]; else { arrayIndex++; [_attributes insertObject:attrs atIndex:arrayIndex]; [_locations insertObject:afterRangeLocation atIndex:arrayIndex]; } arrayIndex--; } else arrayIndex = arraySize - 1; while(arrayIndex > 0 && [[_locations objectAtIndex:arrayIndex-1] unsignedIntValue] >= range.location) { #if 0 NSLog(@"arrayIndex %d", arrayIndex); #endif [_locations removeObjectAtIndex:arrayIndex]; [_attributes removeObjectAtIndex:arrayIndex]; arrayIndex--; } beginRangeLocation = [NSNumber numberWithUnsignedInt:range.location]; location = [[_locations objectAtIndex:arrayIndex] unsignedIntValue]; if(location >= range.location) { if(location > range.location) [_locations replaceObjectAtIndex:arrayIndex withObject:beginRangeLocation]; [_attributes replaceObjectAtIndex:arrayIndex withObject:attributes]; } else { arrayIndex++; [_attributes insertObject:attributes atIndex:arrayIndex]; [_locations insertObject:beginRangeLocation atIndex:arrayIndex]; } #if 0 NSLog(@"setAttributes -> %@", _locations); #endif } - (void) addAttribute:(NSString *)name value:(id)value range:(NSRange)aRange { NSRange effectiveRange; NSDictionary *attrDict; NSMutableDictionary *newDict; unsigned int tmpLength = [self length]; if(NSMaxRange(aRange) > tmpLength) [NSException raise:NSRangeException format:@"RangeError in -addAttribute:%@ value:... range:%@", name, NSStringFromRange(aRange)]; attrDict = [self attributesAtIndex:aRange.location effectiveRange:&effectiveRange]; while(effectiveRange.location < NSMaxRange(aRange)) { effectiveRange = NSIntersectionRange(aRange,effectiveRange); newDict = [[NSMutableDictionary alloc] initWithDictionary:attrDict]; [newDict autorelease]; [newDict setObject:value forKey:name]; [self setAttributes:newDict range:effectiveRange]; if(NSMaxRange(effectiveRange) >= NSMaxRange(aRange)) effectiveRange.location = NSMaxRange(aRange); // stops the loop else if(NSMaxRange(effectiveRange) < tmpLength) attrDict = [self attributesAtIndex:NSMaxRange(effectiveRange) effectiveRange:&effectiveRange]; } } - (void) addAttributes:(NSDictionary *)attributes range:(NSRange)aRange { NSRange effectiveRange; NSDictionary *attrDict; NSMutableDictionary *newDict; unsigned int tmpLength; if(!attributes) [NSException raise:NSInvalidArgumentException format:@"attributes is nil in method -addAttributes:range: in class NSMutableAtrributedString"]; tmpLength = [self length]; if(NSMaxRange(aRange) > tmpLength) [NSException raise:NSRangeException format:@"RangeError in method -addAttributes:... range:%@", NSStringFromRange(aRange)]; attrDict = [self attributesAtIndex:aRange.location effectiveRange:&effectiveRange]; while(effectiveRange.location < NSMaxRange(aRange)) { effectiveRange = NSIntersectionRange(aRange,effectiveRange); newDict = [[NSMutableDictionary alloc] initWithDictionary:attrDict]; [newDict autorelease]; [newDict addEntriesFromDictionary:attributes]; [self setAttributes:newDict range:effectiveRange]; if(NSMaxRange(effectiveRange) >= NSMaxRange(aRange)) effectiveRange.location = NSMaxRange(aRange); // stops the loop... else if(NSMaxRange(effectiveRange) < tmpLength) attrDict = [self attributesAtIndex:NSMaxRange(effectiveRange) effectiveRange:&effectiveRange]; } } - (void) removeAttribute:(NSString *)name range:(NSRange)aRange { NSRange effectiveRange; NSDictionary *attrDict; NSMutableDictionary *newDict; unsigned int tmpLength = [self length]; if(NSMaxRange(aRange) > tmpLength) [NSException raise:NSRangeException format:@"RangeError in method -removeAttribute:%@ range:%@", name, NSStringFromRange(aRange)]; attrDict = [self attributesAtIndex:aRange.location effectiveRange:&effectiveRange]; while(effectiveRange.location < NSMaxRange(aRange)) { effectiveRange = NSIntersectionRange(aRange,effectiveRange); newDict = [[NSMutableDictionary alloc] initWithDictionary:attrDict]; [newDict autorelease]; [newDict removeObjectForKey:name]; [self setAttributes:newDict range:effectiveRange]; if(NSMaxRange(effectiveRange) >= NSMaxRange(aRange)) effectiveRange.location = NSMaxRange(aRange); // stops the loop... else if(NSMaxRange(effectiveRange) < tmpLength) attrDict = [self attributesAtIndex:NSMaxRange(effectiveRange) effectiveRange:&effectiveRange]; } } // Changing characters and attributes - (void) appendAttributedString:(NSAttributedString *)attributedString { [self replaceCharactersInRange:NSMakeRange([self length],0) withAttributedString:attributedString]; } - (void) insertAttributedString:(NSAttributedString *)attributedString atIndex:(unsigned int)index { [self replaceCharactersInRange:NSMakeRange(index,0) withAttributedString:attributedString]; } - (void) replaceCharactersInRange:(NSRange)aRange withAttributedString:(NSAttributedString *)attributedString { NSRange effectiveRange, clipRange, ownRange; NSDictionary *attrDict; #if 0 NSLog(@"astr[%d] replaceCharactersInRange:%@ withAttributedString:str[%d]", [self length], NSStringFromRange(aRange), [attributedString length]); #endif [self replaceCharactersInRange:aRange withString:[attributedString string]]; effectiveRange = NSMakeRange(0,0); clipRange = NSMakeRange(0,[attributedString length]); while(NSMaxRange(effectiveRange) < NSMaxRange(clipRange)) { #if 0 NSLog(@"eff range in=%@", NSStringFromRange(effectiveRange)); #endif attrDict = [attributedString attributesAtIndex:effectiveRange.location effectiveRange:&effectiveRange]; #if 0 NSLog(@"eff range=%@", NSStringFromRange(effectiveRange)); #endif ownRange = NSIntersectionRange(clipRange,effectiveRange); ownRange.location += aRange.location; #if 0 NSLog(@"own range=%@", NSStringFromRange(ownRange)); #endif [self setAttributes:attrDict range:ownRange]; effectiveRange.location += effectiveRange.length; // take next chunk } } - (void) replaceCharactersInRange:(NSRange)range withString:(NSString *)aString { unsigned int tmpLength, arrayIndex, arraySize, moveLocations; NSDictionary *attrs; #if 0 NSLog(@"astr[%d] replaceCharactersInRange:%@ withString:str[%d]", [self length], NSStringFromRange(range), [aString length]); NSLog(@" locations -> %@", _locations); #endif if(!aString) aString = @""; tmpLength = [self length]; #if 0 NSLog(@"maxrange=%u tmpLength=%u", NSMaxRange(range), tmpLength); #endif if(NSMaxRange(range) > tmpLength) { // beyond end of string #if 0 NSLog(@"range=%@", NSStringFromRange(range)); NSLog(@"current string=%@", [self string]); NSLog(@"new string=%@", aString); #endif [NSException raise:NSRangeException format:@"RangeError in -replaceCharactersInRange:%@ withString:%@", NSStringFromRange(range), aString]; } arraySize = [_locations count]; if(NSMaxRange(range) < tmpLength) { // adjust locations NSRange effectiveRange; unsigned int cnt; NSNumber *afterRangeLocation; attrs = _attributesAtIndexEffectiveRange(NSMaxRange(range), &effectiveRange, tmpLength, _attributes, _locations, &arrayIndex); moveLocations = [aString length] - range.length; // how much we have to add afterRangeLocation = [NSNumber numberWithUnsignedInt:NSMaxRange(range)+moveLocations]; #if 0 NSLog(@"arrayIndex = %u", arrayIndex); NSLog(@"effectiveRange = %@", NSStringFromRange(effectiveRange)); NSLog(@"moveLocations = %u", moveLocations); NSLog(@"afterRangeLocation = %@", afterRangeLocation); NSLog(@" loc1 = %@", _locations); #endif if(effectiveRange.location > range.location) [_locations replaceObjectAtIndex:arrayIndex withObject:afterRangeLocation]; else { arrayIndex++; [_attributes insertObject:attrs atIndex:arrayIndex]; [_locations insertObject:afterRangeLocation atIndex:arrayIndex]; arraySize++; // has now one more element } #if 0 NSLog(@" loc1.5 = %@", _locations); #endif for(cnt = arrayIndex + 1; cnt < arraySize; cnt++) { unsigned int newLocation = [[_locations objectAtIndex:cnt] unsignedIntValue] + moveLocations; [_locations replaceObjectAtIndex:cnt withObject:[NSNumber numberWithUnsignedInt:newLocation]]; } arrayIndex--; #if 0 NSLog(@" loc2 = %@", _locations); #endif } else { // replace at or after last character; no change of attribs: last attribute run simply extends to new length arrayIndex = arraySize - 1; } #if 0 NSLog(@"arrayIndex = %u range.location = %u", arrayIndex, range.location); NSLog(@" loc3 = %@", _locations); #endif while(arrayIndex > 0 && [[_locations objectAtIndex:arrayIndex] unsignedIntValue] > range.location) { // delete any location did change attributes in the replaced range [_locations removeObjectAtIndex:arrayIndex]; [_attributes removeObjectAtIndex:arrayIndex]; arrayIndex--; } #if 0 NSLog(@" loc4 = %@", _locations); #endif #if 0 NSLog(@"_string=%@ aString=%@", _string, aString); NSLog(@"len=%d + %d rng=%@", [_string length], [aString length], NSStringFromRange(range)); #endif if(!_string) _string=[aString mutableCopy]; else [_string replaceCharactersInRange:range withString:aString]; #if 0 NSLog(@"len=%d", [_string length]); NSLog(@"_string=%@", _string); #endif #if 0 NSLog(@"replaceCharactersInRangeWithString -> %@", _locations); #endif } - (void) setAttributedString:(NSAttributedString *)attributedString { if(!attributedString) [NSException raise:NSInvalidArgumentException format:@"-setAttributedString:nil"]; [_string setString: [attributedString string]]; [_attributes removeAllObjects]; // remove all existing attributes [_locations removeAllObjects]; [_attributes addObject:[NSDictionary dictionary]]; // create first attribute run [_locations addObject:[NSNumber numberWithUnsignedInt:0]]; _setAttributesFrom(attributedString, NSMakeRange(0, [attributedString length]), _attributes, _locations); } @end /* NSMutableAttributedString */