/** NSAttributedString.m Implementation of string class with attributes Copyright (C) 1997-2022 Free Software Foundation, Inc. Written by: ANOQ of the sun Date: November 1997 Rewrite by: Richard Frith-Macdonald Date: April 1999 This file is part of GNUstep-base This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. If you are interested in a warranty or support for this source code, contact Scott Christley for more information. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. NSAttributedString class reference $Date$ $Revision$ */ /* Warning - [-initWithString:attributes:] is the designated initialiser, * but it doesn't provide any way to perform the function of the * [-initWithAttributedString:] initialiser. * In order to work round this, the string argument of the * designated initialiser has been overloaded such that it * is expected to accept an NSAttributedString here instead of * a string. If you create an NSAttributedString subclass, you * must make sure that your implementation of the initialiser * copes with either an NSString or an NSAttributedString. * If it receives an NSAttributedString, it should ignore the * attributes argument and use the values from the string. */ #import "common.h" #import "GNUstepBase/Unicode.h" #import "Foundation/NSAttributedString.h" #import "Foundation/NSData.h" #import "Foundation/NSException.h" #import "Foundation/NSAutoreleasePool.h" #import "Foundation/NSPortCoder.h" #import "Foundation/NSRange.h" @class GSAttributedString; @interface GSAttributedString : NSObject // Help the compiler @end @class GSMutableAttributedString; @interface GSMutableAttributedString : NSObject // Help the compiler @end @class GSMutableDictionary; @interface GSMutableDictionary : NSObject // Help the compiler @end static Class dictionaryClass = 0; static SEL eqSel; static SEL setSel; static SEL getSel; static SEL allocDictSel; static SEL initDictSel; static SEL addDictSel; static SEL setDictSel; static SEL relDictSel; static SEL remDictSel; static IMP allocDictImp; static IMP initDictImp; static IMP addDictImp; static IMP setDictImp; static IMP relDictImp; static IMP remDictImp; @interface GSMutableAttributedStringTracker : NSMutableString { NSMutableAttributedString *_owner; } + (NSMutableString*) stringWithOwner: (NSMutableAttributedString*)as; @end /** * A string in which name-value pairs represented by an [NSDictionary] may * be associated to ranges of characters. Used for text rendering by the * GUI/AppKit framework, in which fonts, sizes, etc. are stored under standard * attributes in the dictionaries. * */ @implementation NSAttributedString static Class NSAttributedStringClass; static Class GSAttributedStringClass; static Class NSMutableAttributedStringClass; static Class GSMutableAttributedStringClass; + (void) initialize { if (self == [NSAttributedString class]) { NSAttributedStringClass = self; GSAttributedStringClass = [GSAttributedString class]; NSMutableAttributedStringClass = [NSMutableAttributedString class]; GSMutableAttributedStringClass = [GSMutableAttributedString class]; dictionaryClass = [GSMutableDictionary class]; eqSel = @selector(isEqual:); setSel = @selector(setAttributes:range:); getSel = @selector(attributesAtIndex:effectiveRange:); allocDictSel = @selector(allocWithZone:); initDictSel = @selector(initWithDictionary:); addDictSel = @selector(addEntriesFromDictionary:); setDictSel = @selector(setObject:forKey:); relDictSel = @selector(release); remDictSel = @selector(removeObjectForKey:); allocDictImp = [dictionaryClass methodForSelector: allocDictSel]; initDictImp = [dictionaryClass instanceMethodForSelector: initDictSel]; addDictImp = [dictionaryClass instanceMethodForSelector: addDictSel]; setDictImp = [dictionaryClass instanceMethodForSelector: setDictSel]; remDictImp = [dictionaryClass instanceMethodForSelector: remDictSel]; relDictImp = [dictionaryClass instanceMethodForSelector: relDictSel]; } } + (id) allocWithZone: (NSZone*)z { if (self == NSAttributedStringClass) return NSAllocateObject(GSAttributedStringClass, 0, z); else return NSAllocateObject(self, 0, z); } - (Class) classForCoder { return NSAttributedStringClass; } static void appendUIntData(NSMutableData *d, NSUInteger i) { unsigned int aux = i; unsigned int len = 1; while (aux >= 128) { aux /= 128; len++; } { unsigned char *p, buf[len]; p = buf; while (i >= 128) { *p++ = (i & 0x7f) + 128; i /= 128; } *p = i; [d appendBytes: buf length: len]; } } - (void) encodeWithCoder: (NSCoder*)aCoder { if ([aCoder allowsKeyedCoding]) { NSUInteger length = [self length]; [aCoder encodeObject: [self string] forKey: @"NSString"]; if (length > 0) { NSRange range; NSDictionary *attrs; attrs = [self attributesAtIndex: 0 effectiveRange: &range]; if (range.length == length) { [aCoder encodeObject: attrs forKey: @"NSAttributes"]; } else { NSUInteger i = 0; NSUInteger pos = 0; NSMutableArray *attrs = [NSMutableArray arrayWithCapacity: 1]; NSMutableData *info = [NSMutableData dataWithCapacity: 2]; while (pos < length) { [attrs addObject: [self attributesAtIndex: pos effectiveRange: &range]]; appendUIntData(info, range.length); appendUIntData(info, i++); pos = NSMaxRange(range); } [aCoder encodeObject: [[attrs copy] autorelease] forKey: @"NSAttributes"]; [aCoder encodeObject: [[info copy] autorelease] forKey: @"NSAttributeInfo"]; } } } else { NSRange r = NSMakeRange(0, 0); unsigned index = NSMaxRange(r); unsigned length = [self length]; NSString *string = [self string]; NSDictionary *attrs; [aCoder encodeObject: string]; while (index < length) { attrs = [self attributesAtIndex: index effectiveRange: &r]; index = NSMaxRange(r); [aCoder encodeValueOfObjCType: @encode(unsigned) at: &index]; [aCoder encodeObject: attrs]; } } } - (id) initWithCoder: (NSCoder*)aDecoder { if ([aDecoder allowsKeyedCoding]) { NSString *string = [aDecoder decodeObjectForKey: @"NSString"]; if (![aDecoder containsValueForKey: @"NSAttributeInfo"]) { NSDictionary *attributes; attributes = [aDecoder decodeObjectForKey: @"NSAttributes"]; self = [self initWithString: string attributes: attributes]; } else { NSArray *attributes = [aDecoder decodeObjectForKey: @"NSAttributes"]; NSData *info = [aDecoder decodeObjectForKey: @"NSAttributeInfo"]; unsigned int pos = 0; const unsigned char *p = [info bytes]; const unsigned char *end = p + [info length]; NSMutableAttributedString *m = [[NSMutableAttributedString alloc] initWithString: string attributes: nil]; while (p < end) { unsigned int idx; unsigned int len; unsigned int shift; NSRange r; len = shift = 0; while (*p & 0x80) { len += (*p++ - 128) << shift; shift += 7; } len += *p++ << shift; idx = shift = 0; while (*p & 0x80) { idx += (*p++ - 128) << shift; shift += 7; } idx += *p++ << shift; r = NSMakeRange(pos, len); [m setAttributes: [attributes objectAtIndex: idx] range: r]; pos = NSMaxRange(r); } DESTROY(self); self = [m copy]; RELEASE(m); } } else { NSString *string = [aDecoder decodeObject]; unsigned length = [string length]; if (length == 0) { self = [self initWithString: string attributes: nil]; } else { unsigned index; NSDictionary *attrs; [aDecoder decodeValueOfObjCType: @encode(unsigned) at: &index]; attrs = [aDecoder decodeObject]; if (index == length) { self = [self initWithString: string attributes: attrs]; } else { NSRange r = NSMakeRange(0, index); unsigned last = index; NSMutableAttributedString *m; m = [NSMutableAttributedString alloc]; m = [m initWithString: string attributes: nil]; [m setAttributes: attrs range: r]; while (index < length) { [aDecoder decodeValueOfObjCType: @encode(unsigned) at: &index]; attrs = [aDecoder decodeObject]; r = NSMakeRange(last, index - last); [m setAttributes: attrs range: r]; last = index; } DESTROY(self); self = [m copy]; RELEASE(m); } } } return self; } - (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder { if ([aCoder isByref] == NO) return self; return [super replacementObjectForPortCoder: aCoder]; } //NSCopying protocol - (id) copyWithZone: (NSZone*)zone { if ([self isKindOfClass: [NSMutableAttributedString class]] || NSShouldRetainWithZone(self, zone) == NO) return [[GSAttributedStringClass allocWithZone: zone] initWithAttributedString: self]; else return RETAIN(self); } //NSMutableCopying protocol - (id) mutableCopyWithZone: (NSZone*)zone { return [[GSMutableAttributedStringClass allocWithZone: zone] initWithAttributedString: self]; } //Creating an NSAttributedString - (id) init { return [self initWithString: @"" attributes: nil]; } /** * Initialize to aString with no attributes. */ - (id) initWithString: (NSString*)aString { return [self initWithString: aString attributes: nil]; } /** * Initialize to copy of attributedString. */ - (id) initWithAttributedString: (NSAttributedString*)attributedString { return [self initWithString: (NSString*)attributedString attributes: nil]; } /** * Initialize to aString with given attributes applying over full range of * string. */ - (id) initWithString: (NSString*)aString attributes: (NSDictionary*)attributes { //This is the designated initializer [self subclassResponsibility: _cmd];/* Primitive method! */ return nil; } - (NSString*) description { NSRange r = NSMakeRange(0, 0); unsigned index = NSMaxRange(r); unsigned length = [self length]; NSString *string = [self string]; NSDictionary *attrs; NSMutableString *desc; desc = [NSMutableString stringWithCapacity: length]; while (index < length && (attrs = [self attributesAtIndex: index effectiveRange: &r]) != nil) { index = NSMaxRange(r); [desc appendFormat: @"%@%@", [string substringWithRange: r], attrs]; } return desc; } //Retrieving character information /** * Return length of the underlying string. */ - (NSUInteger) length { return [[self string] length]; } /** * Return the underlying string, stripped of attributes. */ - (NSString*) string { [self subclassResponsibility: _cmd];/* Primitive method! */ return nil; } //Retrieving attribute information /** * Returns attributes and values at index, and, if effectiveRange * is non-nil, this gets filled with a range over which these attributes * and values still hold. This may not be the maximum range, depending * on the implementation. */ - (NSDictionary*) attributesAtIndex: (NSUInteger)index effectiveRange: (NSRange*)aRange { [self subclassResponsibility: _cmd];/* Primitive method! */ *aRange = NSMakeRange(NSNotFound, 0); return nil; } /** * Returns attributes and values at index, and, if longestEffectiveRange * is non-nil, this gets filled with the range over which the attribute-value * set is the same as at index, clipped to rangeLimit. */ - (NSDictionary*) attributesAtIndex: (NSUInteger)index longestEffectiveRange: (NSRange*)aRange inRange: (NSRange)rangeLimit { NSDictionary *attrDictionary, *tmpDictionary; NSRange tmpRange; IMP getImp; if (NSMaxRange(rangeLimit) > [self length]) { [NSException raise: NSRangeException format: @"RangeError in method -attributesAtIndex:longestEffectiveRange:inRange: in class NSAttributedString"]; } getImp = [self methodForSelector: getSel]; attrDictionary = (*getImp)(self, getSel, index, aRange); if (aRange == 0) return attrDictionary; while (aRange->location > rangeLimit.location) { //Check extend range backwards tmpDictionary = (*getImp)(self, getSel, aRange->location-1, &tmpRange); if ([tmpDictionary isEqualToDictionary: attrDictionary]) { aRange->length = NSMaxRange(*aRange) - tmpRange.location; aRange->location = tmpRange.location; } else { break; } } while (NSMaxRange(*aRange) < NSMaxRange(rangeLimit)) { //Check extend range forwards tmpDictionary = (*getImp)(self, getSel, NSMaxRange(*aRange), &tmpRange); if ([tmpDictionary isEqualToDictionary: attrDictionary]) { aRange->length = NSMaxRange(tmpRange) - aRange->location; } else { break; } } *aRange = NSIntersectionRange(*aRange,rangeLimit);//Clip to rangeLimit return attrDictionary; } /** * Returns value for given attribute at index, and, if effectiveRange is * non-nil, this gets filled with a range over which this value holds. This * may not be the maximum range, depending on the implementation. */ - (id) attribute: (NSString*)attributeName atIndex: (NSUInteger)index effectiveRange: (NSRange*)aRange { NSDictionary *tmpDictionary; id attrValue; tmpDictionary = [self attributesAtIndex: index effectiveRange: aRange]; if (attributeName == nil) { if (aRange != 0) { *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; } attrValue = [tmpDictionary objectForKey: attributeName]; return attrValue; } /** * Returns value for given attribute at index, and, if longestEffectiveRange * is non-nil, this gets filled with the range over which the attribute * applies, clipped to rangeLimit. */ - (id) attribute: (NSString*)attributeName atIndex: (NSUInteger)index longestEffectiveRange: (NSRange*)aRange inRange: (NSRange)rangeLimit { NSDictionary *tmpDictionary; id attrValue; id tmpAttrValue; NSRange tmpRange; BOOL (*eImp)(id,SEL,id); IMP getImp; if (NSMaxRange(rangeLimit) > [self length]) { [NSException raise: NSRangeException format: @"RangeError in method %@ in class %@", NSStringFromSelector(_cmd), NSStringFromClass([self class])]; } if (attributeName == nil) return nil; attrValue = [self attribute: attributeName atIndex: index effectiveRange: aRange]; if (aRange == 0) return attrValue; /* * If attrValue == nil then eImp will be zero */ eImp = (BOOL(*)(id,SEL,id))[attrValue methodForSelector: eqSel]; getImp = [self methodForSelector: getSel]; while (aRange->location > rangeLimit.location) { //Check extend range backwards tmpDictionary = (*getImp)(self, getSel, aRange->location-1, &tmpRange); tmpAttrValue = [tmpDictionary objectForKey: attributeName]; if (tmpAttrValue == attrValue || (eImp != 0 && (*eImp)(attrValue, eqSel, tmpAttrValue))) { aRange->length = NSMaxRange(*aRange) - tmpRange.location; aRange->location = tmpRange.location; } else { break; } } while (NSMaxRange(*aRange) < NSMaxRange(rangeLimit)) { //Check extend range forwards tmpDictionary = (*getImp)(self, getSel, NSMaxRange(*aRange), &tmpRange); tmpAttrValue = [tmpDictionary objectForKey: attributeName]; if (tmpAttrValue == attrValue || (eImp != 0 && (*eImp)(attrValue, eqSel, tmpAttrValue))) { aRange->length = NSMaxRange(tmpRange) - aRange->location; } else { break; } } *aRange = NSIntersectionRange(*aRange,rangeLimit);//Clip to rangeLimit return attrValue; } //Comparing attributed strings /** * Returns whether all characters and attributes are equal between this * string and otherString. */ - (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: NSAttributedStringClass]) return [self isEqualToAttributedString: anObject]; return NO; } //Extracting a substring /** * Returns substring with attribute information. */ - (NSAttributedString*) attributedSubstringFromRange: (NSRange)aRange { NSAttributedString *newAttrString; NSString *newSubstring; NSDictionary *attrs; NSRange range; unsigned len = [self length]; GS_RANGE_CHECK(aRange, len); newSubstring = [[self string] substringWithRange: aRange]; attrs = [self attributesAtIndex: aRange.location effectiveRange: &range]; range = NSIntersectionRange(range, aRange); if (NSEqualRanges(range, aRange) == YES) { newAttrString = [GSAttributedStringClass alloc]; newAttrString = [newAttrString initWithString: newSubstring attributes: attrs]; } else { NSMutableAttributedString *m; NSRange rangeToSet = range; m = [GSMutableAttributedStringClass alloc]; m = [m initWithString: newSubstring attributes: nil]; rangeToSet.location = 0; [m setAttributes: attrs range: rangeToSet]; while (NSMaxRange(range) < NSMaxRange(aRange)) { attrs = [self attributesAtIndex: NSMaxRange(range) effectiveRange: &range]; rangeToSet = NSIntersectionRange(range, aRange); rangeToSet.location -= aRange.location; [m setAttributes: attrs range: rangeToSet]; } newAttrString = [m copy]; RELEASE(m); } IF_NO_ARC(AUTORELEASE(newAttrString);) return newAttrString; } @end //NSAttributedString /** * Mutable version of [NSAttributedString]. */ @implementation NSMutableAttributedString + (id) allocWithZone: (NSZone*)z { if (self == NSMutableAttributedStringClass) return NSAllocateObject(GSMutableAttributedStringClass, 0, z); else return NSAllocateObject(self, 0, z); } - (Class) classForCoder { return NSMutableAttributedStringClass; } - (id) initWithCoder: (NSCoder*)aDecoder { if ([aDecoder allowsKeyedCoding]) { NSString *string = [aDecoder decodeObjectForKey: @"NSString"]; if (![aDecoder containsValueForKey: @"NSAttributeInfo"]) { NSDictionary *attributes; attributes = [aDecoder decodeObjectForKey: @"NSAttributes"]; self = [self initWithString: string attributes: attributes]; } else { NSArray *attributes = [aDecoder decodeObjectForKey: @"NSAttributes"]; NSData *info = [aDecoder decodeObjectForKey: @"NSAttributeInfo"]; unsigned int pos = 0; const unsigned char *p = [info bytes]; const unsigned char *end = p + [info length]; self = [self initWithString: string attributes: nil]; while (p < end) { unsigned int idx; unsigned int len; unsigned int shift; NSRange r; len = shift = 0; while (*p & 0x80) { len += (*p++ - 128) << shift; shift += 7; } len += *p++ << shift; idx = shift = 0; while (*p & 0x80) { idx += (*p++ - 128) << shift; shift += 7; } idx += *p++ << shift; r = NSMakeRange(pos, len); [self setAttributes: [attributes objectAtIndex: idx] range: r]; pos = NSMaxRange(r); } } } else { NSString *string = [aDecoder decodeObject]; unsigned length = [string length]; if (length == 0) { self = [self initWithString: string attributes: nil]; } else { unsigned index; NSDictionary *attrs; [aDecoder decodeValueOfObjCType: @encode(unsigned) at: &index]; attrs = [aDecoder decodeObject]; if (index == length) { self = [self initWithString: string attributes: attrs]; } else { NSRange r = NSMakeRange(0, index); unsigned last = index; self = [self initWithString: string attributes: nil]; [self setAttributes: attrs range: r]; while (index < length) { [aDecoder decodeValueOfObjCType: @encode(unsigned) at: &index]; attrs = [aDecoder decodeObject]; r = NSMakeRange(last, index - last); [self setAttributes: attrs range: r]; last = index; } } } } return self; } //Retrieving character information /** * Returns mutable version of the underlying string. */ - (NSMutableString*) mutableString { return [GSMutableAttributedStringTracker stringWithOwner: self]; } //Changing characters /** * Removes characters and attributes applying to them. */ - (void) deleteCharactersInRange: (NSRange)aRange { [self replaceCharactersInRange: aRange withString: nil]; } //Changing attributes /** * Sets attributes to apply over range, replacing any previous attributes. */ - (void) setAttributes: (NSDictionary*)attributes range: (NSRange)aRange { [self subclassResponsibility: _cmd];// Primitive method! } /** * Adds attribute applying to given range. */ - (void) addAttribute: (NSString*)name value: (id)value range: (NSRange)aRange { NSRange effectiveRange; NSDictionary *attrDict; NSMutableDictionary *newDict; unsigned int tmpLength; IMP getImp; tmpLength = [self length]; GS_RANGE_CHECK(aRange, tmpLength); getImp = [self methodForSelector: getSel]; attrDict = (*getImp)(self, getSel, aRange.location, &effectiveRange); if (effectiveRange.location < NSMaxRange(aRange)) { IMP setImp; setImp = [self methodForSelector: setSel]; [self beginEditing]; while (effectiveRange.location < NSMaxRange(aRange)) { effectiveRange = NSIntersectionRange(aRange, effectiveRange); newDict = (*allocDictImp)(dictionaryClass, allocDictSel, NSDefaultMallocZone()); newDict = (*initDictImp)(newDict, initDictSel, attrDict); (*setDictImp)(newDict, setDictSel, value, name); (*setImp)(self, setSel, newDict, effectiveRange); IF_NO_ARC((*relDictImp)(newDict, relDictSel);) if (NSMaxRange(effectiveRange) >= NSMaxRange(aRange)) { effectiveRange.location = NSMaxRange(aRange);// stop the loop... } else if (NSMaxRange(effectiveRange) < tmpLength) { attrDict = (*getImp)(self, getSel, NSMaxRange(effectiveRange), &effectiveRange); } } [self endEditing]; } } /** * Add attributes to apply over given range. */ - (void) addAttributes: (NSDictionary*)attributes range: (NSRange)aRange { NSRange effectiveRange; NSDictionary *attrDict; NSMutableDictionary *newDict; unsigned int tmpLength; IMP getImp; 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 -addAttribute:value:range: " @"in class NSMutableAttributedString"]; } getImp = [self methodForSelector: getSel]; attrDict = (*getImp)(self, getSel, aRange.location, &effectiveRange); if (effectiveRange.location < NSMaxRange(aRange)) { IMP setImp; setImp = [self methodForSelector: setSel]; [self beginEditing]; while (effectiveRange.location < NSMaxRange(aRange)) { effectiveRange = NSIntersectionRange(aRange,effectiveRange); newDict = (*allocDictImp)(dictionaryClass, allocDictSel, NSDefaultMallocZone()); newDict = (*initDictImp)(newDict, initDictSel, attrDict); (*addDictImp)(newDict, addDictSel, attributes); (*setImp)(self, setSel, newDict, effectiveRange); IF_NO_ARC((*relDictImp)(newDict, relDictSel);) if (NSMaxRange(effectiveRange) >= NSMaxRange(aRange)) { effectiveRange.location = NSMaxRange(aRange);// stop the loop... } else if (NSMaxRange(effectiveRange) < tmpLength) { attrDict = (*getImp)(self, getSel, NSMaxRange(effectiveRange), &effectiveRange); } } [self endEditing]; } } /** * Removes given attribute from aRange. */ - (void) removeAttribute: (NSString*)name range: (NSRange)aRange { NSRange effectiveRange; NSDictionary *attrDict; NSMutableDictionary *newDict; unsigned int tmpLength; IMP getImp; tmpLength = [self length]; GS_RANGE_CHECK(aRange, tmpLength); getImp = [self methodForSelector: getSel]; attrDict = (*getImp)(self, getSel, aRange.location, &effectiveRange); if (effectiveRange.location < NSMaxRange(aRange)) { IMP setImp; setImp = [self methodForSelector: setSel]; [self beginEditing]; while (effectiveRange.location < NSMaxRange(aRange)) { effectiveRange = NSIntersectionRange(aRange,effectiveRange); newDict = (*allocDictImp)(dictionaryClass, allocDictSel, NSDefaultMallocZone()); newDict = (*initDictImp)(newDict, initDictSel, attrDict); (*remDictImp)(newDict, remDictSel, name); (*setImp)(self, setSel, newDict, effectiveRange); IF_NO_ARC((*relDictImp)(newDict, relDictSel);) if (NSMaxRange(effectiveRange) >= NSMaxRange(aRange)) { effectiveRange.location = NSMaxRange(aRange);// stop the loop... } else if (NSMaxRange(effectiveRange) < tmpLength) { attrDict = (*getImp)(self, getSel, NSMaxRange(effectiveRange), &effectiveRange); } } [self endEditing]; } } //Changing characters and attributes /** * Appends attributed string to end of this one, preserving attributes. */ - (void) appendAttributedString: (NSAttributedString*)attributedString { [self replaceCharactersInRange: NSMakeRange([self length],0) withAttributedString: attributedString]; } /** * Inserts attributed string within this one, preserving attributes. */ - (void) insertAttributedString: (NSAttributedString*)attributedString atIndex: (NSUInteger)index { [self replaceCharactersInRange: NSMakeRange(index,0) withAttributedString: attributedString]; } /** * Replaces substring and attributes. */ - (void) replaceCharactersInRange: (NSRange)aRange withAttributedString: (NSAttributedString*)attributedString { NSDictionary *attrDict; NSString *tmpStr; unsigned max; if (attributedString == nil) { [self replaceCharactersInRange: aRange withString: nil]; return; } [self beginEditing]; tmpStr = [attributedString string]; [self replaceCharactersInRange: aRange withString: tmpStr]; max = [tmpStr length]; if (max > 0) { unsigned loc = 0; NSRange effectiveRange = NSMakeRange(0, loc); NSRange clipRange = NSMakeRange(0, max); IMP getImp; IMP setImp; getImp = [attributedString methodForSelector: getSel]; setImp = [self methodForSelector: setSel]; while (loc < max) { NSRange ownRange; attrDict = (*getImp)(attributedString, getSel, loc, &effectiveRange); ownRange = NSIntersectionRange(clipRange, effectiveRange); ownRange.location += aRange.location; (*setImp)(self, setSel, attrDict, ownRange); loc = NSMaxRange(effectiveRange); } } [self endEditing]; } /** * Replaces substring; replacement is granted attributes equal to those of * the first character of the portion replaced. */ - (void) replaceCharactersInRange: (NSRange)aRange withString: (NSString*)aString { [self subclassResponsibility: _cmd];// Primitive method! } /** * Replaces entire contents (so this object can be reused). */ - (void) setAttributedString: (NSAttributedString*)attributedString { [self replaceCharactersInRange: NSMakeRange(0,[self length]) withAttributedString: attributedString]; } /** * Call before executing a collection of changes, for optimization. */ - (void) beginEditing { } /** * Call after executing a collection of changes, for optimization. */ - (void) endEditing { } @end //NSMutableAttributedString /* * The GSMutableAttributedStringTracker class is a concrete subclass of * NSMutableString which keeps it's owner informed of any changes made * to it. */ @implementation GSMutableAttributedStringTracker + (NSMutableString*) stringWithOwner: (NSMutableAttributedString*)as { GSMutableAttributedStringTracker *str; NSZone *z = NSDefaultMallocZone(); str = (GSMutableAttributedStringTracker*) NSAllocateObject(self, 0, z); str->_owner = RETAIN(as); return AUTORELEASE(str); } - (void) dealloc { RELEASE(_owner); [super dealloc]; } - (NSUInteger) length { return [[_owner string] length]; } - (unichar) characterAtIndex: (NSUInteger)index { return [[_owner string] characterAtIndex: index]; } - (void)getCharacters: (unichar*)buffer { [[_owner string] getCharacters: buffer]; } - (void)getCharacters: (unichar*)buffer range: (NSRange)aRange { [[_owner string] getCharacters: buffer range: aRange]; } - (const char*) cString { return [[_owner string] cString]; } - (NSUInteger) cStringLength { return [[_owner string] cStringLength]; } - (NSStringEncoding) fastestEncoding { return [[_owner string] fastestEncoding]; } - (NSStringEncoding) smallestEncoding { return [[_owner string] smallestEncoding]; } - (int) _baseLength { return [[_owner string] _baseLength]; } - (void) encodeWithCoder: (NSCoder*)aCoder { [[_owner string] encodeWithCoder: aCoder]; } - (Class) classForCoder { return [[_owner string] classForCoder]; } - (void) replaceCharactersInRange: (NSRange)aRange withString: (NSString*)aString { [_owner replaceCharactersInRange: aRange withString: aString]; } @end