| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See http://swift.org/LICENSE.txt for license information |
| // See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| |
| |
| /* CFAttributedString.c |
| Copyright (c) 2004-2015, Apple Inc. All rights reserved. |
| */ |
| |
| #include <CoreFoundation/CFBase.h> |
| #include <CoreFoundation/CFAttributedString.h> |
| #include "CFRunArray.h" |
| #include <CoreFoundation/ForFoundationOnly.h> |
| #include "CFInternal.h" |
| |
| struct __CFAttributedString { |
| CFRuntimeBase base; |
| CFStringRef string; |
| CFRunArrayRef attributeArray; |
| }; |
| |
| /* Mutability is determined by a bit in the CF base. Mutable if bit 0 is 0. So by default freshly created attributed strings are mutable. Don't change mutability once the object has been created and initialized! |
| */ |
| CF_INLINE Boolean __CFAttributedStringIsMutable(CFAttributedStringRef attrStr) { |
| return __CFBitfieldGetValue(((const CFRuntimeBase *)attrStr)->_cfinfo[CF_INFO_BITS], 0, 0) ? false : true; |
| } |
| |
| CF_INLINE void __CFAttributedStringSetMutable(CFAttributedStringRef attrStr, Boolean flag) { |
| __CFBitfieldSetValue(((CFRuntimeBase *)attrStr)->_cfinfo[CF_INFO_BITS], 0, 0, (flag ? 0 : 1)); |
| } |
| |
| |
| /* Assertions |
| */ |
| #define __CFAssertIsAttributedString(cf) __CFGenericValidateType(cf, CFAttributedStringGetTypeID()) |
| #define __CFAssertIndexIsInBounds(cf, idx) CFAssert3((idx) >= 0 && (idx) < CFAttributedStringGetLength(cf), __kCFLogAssertion, "%s(): index %d out of bounds (length %d)", __PRETTY_FUNCTION__, idx, CFAttributedStringGetLength(cf)) |
| #define __CFAssertRangeIsInBounds(cf, idx, count) CFAssert4((idx) >= 0 && (idx + count) <= CFAttributedStringGetLength(cf), __kCFLogAssertion, "%s(): range %d,%d out of bounds (length %d)", __PRETTY_FUNCTION__, idx, count, CFAttributedStringGetLength(cf)) |
| #define __CFAssertRangeIsWithinLength(len, idx, count) CFAssert4((idx) >= 0 && (idx + count) <= len, __kCFLogAssertion, "%s(): range %d,%d out of bounds (length %d)", __PRETTY_FUNCTION__, idx, count, len) |
| #define __CFAssertIsAttributedStringAndMutable(cf) CFAssert1((CFGetTypeID(cf) == CFAttributedStringGetTypeID()) && __CFAttributedStringIsMutable(cf), __kCFLogAssertion, "%s(): argument not a CFMutableAttributedString", __PRETTY_FUNCTION__) |
| |
| |
| /*** "Polymorphic" functions ***/ |
| |
| static Boolean __CFAttributedStringEqual(CFTypeRef cf1, CFTypeRef cf2) { |
| CFAttributedStringRef attrStr = (CFAttributedStringRef)cf1; |
| CFAttributedStringRef otherAttrStr = (CFAttributedStringRef)cf2; |
| |
| if (!CFEqual(attrStr->string, CFAttributedStringGetString(otherAttrStr))) return false; |
| |
| CFIndex len = CFStringGetLength(attrStr->string); |
| CFIndex curLoc = 0; |
| CFRange myRange = {0, 0}; |
| CFRange otherRange = {0, 0}; |
| CFDictionaryRef myAttrs = NULL; |
| CFDictionaryRef otherAttrs = NULL; |
| |
| while (curLoc < len) { |
| if (curLoc >= myRange.length + myRange.location) myAttrs = CFAttributedStringGetAttributes(attrStr, curLoc, &myRange); |
| if (curLoc >= otherRange.length + otherRange.location) otherAttrs = CFAttributedStringGetAttributes(otherAttrStr, curLoc, &otherRange); |
| if (!CFEqual(myAttrs, otherAttrs)) return false; |
| curLoc = otherRange.length + otherRange.location; |
| if (myRange.length + myRange.location < curLoc) curLoc = myRange.length + myRange.location; |
| } |
| return true; |
| } |
| |
| static CFHashCode __CFAttributedStringHash(CFTypeRef cf) { |
| CFAttributedStringRef attrStr = (CFAttributedStringRef)cf; |
| return CFHash(attrStr->string); |
| } |
| |
| // Create a stack or malloc'ed array of CFTypeRef |
| |
| #define localArrayStackSize 256 |
| |
| #define createLocalArray(array, count) \ |
| CFTypeRef array ## Buf[localArrayStackSize]; \ |
| CFTypeRef *array = (count <= localArrayStackSize) ? (array ## Buf) : ((count < LONG_MAX / sizeof(CFTypeRef)) ? malloc(count * sizeof(CFTypeRef)) : NULL); |
| |
| #define freeLocalArray(array) \ |
| if (array != array ## Buf) free(array); |
| |
| static CFStringRef __CFAttributedStringCopyDescription(CFTypeRef cf) { |
| CFMutableStringRef result = CFStringCreateMutable(kCFAllocatorSystemDefault, 0); |
| CFAttributedStringRef attrStr = (CFAttributedStringRef)cf; |
| CFIndex len = CFStringGetLength(attrStr->string); |
| CFRange range = {0, 0}; |
| while (range.location < len) { |
| CFDictionaryRef attrs = CFAttributedStringGetAttributes(attrStr, range.location, &range); |
| CFStringRef substring = CFStringCreateWithSubstring(kCFAllocatorSystemDefault, attrStr->string, range); |
| CFStringAppend(result, substring); |
| CFRelease(substring); |
| CFStringAppendFormat(result, NULL, CFSTR(" %p {"), attrs); |
| CFIndex cnt = CFDictionaryGetCount(attrs); |
| createLocalArray(keys, cnt); |
| createLocalArray(values, cnt); |
| CFDictionaryGetKeysAndValues(attrs, keys, values); |
| while (cnt--) CFStringAppendFormat(result, NULL, CFSTR("%@=%@%s"), keys[cnt], values[cnt], ((cnt == 0) ? "" : ", ")); |
| CFStringAppendFormat(result, NULL, CFSTR("} Len %ld\n"), (long)range.length); |
| range.location += range.length; |
| freeLocalArray(keys); |
| freeLocalArray(values); |
| } |
| return result; |
| } |
| |
| static void __CFAttributedStringDeallocate(CFTypeRef cf) { |
| CFAttributedStringRef attrStr = (CFAttributedStringRef)cf; |
| CFRelease(attrStr->string); |
| CFRelease(attrStr->attributeArray); |
| } |
| |
| static CFTypeID __kCFAttributedStringTypeID = _kCFRuntimeNotATypeID; |
| |
| static const CFRuntimeClass __CFAttributedStringClass = { |
| 0, |
| "CFAttributedString", |
| NULL, // init |
| NULL, // copy |
| __CFAttributedStringDeallocate, |
| __CFAttributedStringEqual, |
| __CFAttributedStringHash, |
| NULL, // |
| __CFAttributedStringCopyDescription |
| }; |
| |
| CFTypeID CFAttributedStringGetTypeID(void) { |
| static dispatch_once_t initOnce; |
| dispatch_once(&initOnce, ^{ __kCFAttributedStringTypeID = _CFRuntimeRegisterClass(&__CFAttributedStringClass); }); |
| return __kCFAttributedStringTypeID; |
| } |
| |
| |
| /*** Creation and Copy routines ***/ |
| |
| /* Create an "internal" attributes dictionary |
| */ |
| static CFMutableDictionaryRef __CFAttributedStringCreateAttributesDictionary(CFAllocatorRef alloc, CFDictionaryRef attrs) { |
| if (attrs) { |
| return CFDictionaryCreateMutableCopy(alloc, 0, attrs); |
| } else { |
| return CFDictionaryCreateMutable(alloc, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); |
| } |
| } |
| |
| /* Does no argument checking; doesn't shortcut to doing a copy if the range is the whole string. (This is used by the other functions to create copies). |
| */ |
| CFMutableAttributedStringRef __CFAttributedStringCreateMutableWithSubstring(CFAllocatorRef alloc, CFAttributedStringRef attrStr, CFRange range) { |
| CFMutableAttributedStringRef newAttrStr = CFAttributedStringCreateMutable(alloc, 0); |
| |
| // Initialize the string (!!! this should be done more efficiently!) |
| CFStringRef str = CFStringCreateWithSubstring(alloc, attrStr->string, range); |
| CFAttributedStringReplaceString(newAttrStr, CFRangeMake(0, 0), str); |
| CFRelease(str); |
| |
| CFIndex curLoc = range.location; |
| CFIndex endLoc = range.location + range.length; |
| |
| while (curLoc < endLoc) { |
| CFRange effectiveRange; |
| CFDictionaryRef attrs = CFAttributedStringGetAttributes(attrStr, curLoc, &effectiveRange); |
| if (curLoc != effectiveRange.location) effectiveRange.length -= (curLoc - effectiveRange.location); |
| if (curLoc + effectiveRange.length > endLoc) effectiveRange.length = endLoc - curLoc; |
| CFAttributedStringSetAttributes(newAttrStr, CFRangeMake(curLoc - range.location, effectiveRange.length), attrs, true); |
| curLoc += effectiveRange.length; |
| } |
| |
| return newAttrStr; |
| } |
| |
| |
| CFAttributedStringRef CFAttributedStringCreate(CFAllocatorRef alloc, CFStringRef str, CFDictionaryRef attributes) { |
| CFIndex size = sizeof(struct __CFAttributedString) - sizeof(CFRuntimeBase); |
| struct __CFAttributedString *newAttrStr = (struct __CFAttributedString *)_CFRuntimeCreateInstance(alloc, CFAttributedStringGetTypeID(), size, NULL); |
| |
| if (newAttrStr == NULL) return NULL; |
| |
| newAttrStr->string = CFStringCreateCopy(alloc, str); |
| newAttrStr->attributeArray = CFRunArrayCreate(alloc); |
| |
| CFIndex len = CFStringGetLength(newAttrStr->string); |
| if (len) { |
| CFMutableDictionaryRef attrs = __CFAttributedStringCreateAttributesDictionary(alloc, attributes); |
| CFRunArrayInsert(newAttrStr->attributeArray, CFRangeMake(0, len), attrs); |
| CFRelease(attrs); |
| } |
| |
| __CFAttributedStringSetMutable(newAttrStr, false); // Make it immutable |
| |
| return (CFAttributedStringRef)newAttrStr; |
| } |
| |
| CFAttributedStringRef CFAttributedStringCreateWithSubstring(CFAllocatorRef alloc, CFAttributedStringRef attrStr, CFRange range) { |
| // CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), CFAttributedStringRef, (NSAttributedString *)attrStr, _createAttributedSubstringWithRange:NSMakeRange(range.location, range.length)); |
| |
| __CFAssertIsAttributedString(attrStr); |
| __CFAssertRangeIsInBounds(attrStr, range.location, range.length); |
| |
| CFIndex len = CFAttributedStringGetLength(attrStr); |
| |
| // !!! Bounds check on range here |
| |
| if (range.location == 0 && range.length == len) { |
| return CFAttributedStringCreateCopy(alloc, attrStr); /* This in turn might take a retain shortcut */ |
| } else { |
| struct __CFAttributedString *newAttrStr = __CFAttributedStringCreateMutableWithSubstring(alloc, attrStr, range); |
| __CFAttributedStringSetMutable(newAttrStr, false); |
| return (CFAttributedStringRef)newAttrStr; |
| } |
| } |
| |
| /* CFAttributedStringCreateCopy() will do a refcount bump if the argument is immutable and has the same allocator |
| */ |
| CFAttributedStringRef CFAttributedStringCreateCopy(CFAllocatorRef alloc, CFAttributedStringRef attrStr) { |
| // CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), CFAttributedStringRef, (NSAttributedString *)attrStr, copy); |
| |
| __CFAssertIsAttributedString(attrStr); |
| if (!__CFAttributedStringIsMutable(attrStr) && // If the string is not mutable |
| ((alloc ? alloc : __CFGetDefaultAllocator()) == __CFGetAllocator(attrStr))) { // and it has the same allocator as the one we're using |
| CFRetain(attrStr); // Then just retain instead of making a true copy |
| return attrStr; |
| } else { |
| CFIndex len = CFAttributedStringGetLength(attrStr); |
| CFMutableAttributedStringRef newAttrStr =__CFAttributedStringCreateMutableWithSubstring(alloc, attrStr, CFRangeMake(0, len)); |
| __CFAttributedStringSetMutable(newAttrStr, false); // Make it immutable |
| return (CFAttributedStringRef)newAttrStr; |
| } |
| } |
| |
| /* CFAttributedStringCreateWithRuns() creates an attributed string from the specified string and a list of sparse attribute dictionaries. See CFAttributedStringPriv.h for info. |
| */ |
| CFAttributedStringRef _CFAttributedStringCreateWithRuns(CFAllocatorRef alloc, CFStringRef str, const CFDictionaryRef *attrDictionaries, const CFRange *runRanges, CFIndex numRuns) { |
| CFIndex size = sizeof(struct __CFAttributedString) - sizeof(CFRuntimeBase); |
| struct __CFAttributedString *newAttrStr = (struct __CFAttributedString *)_CFRuntimeCreateInstance(alloc, CFAttributedStringGetTypeID(), size, NULL); |
| |
| if (newAttrStr == NULL) return NULL; |
| |
| newAttrStr->string = CFStringCreateCopy(alloc, str); |
| newAttrStr->attributeArray = CFRunArrayCreate(alloc); |
| |
| // First fill attributed string with empty attribute dictionary |
| CFIndex len = CFStringGetLength(newAttrStr->string); |
| if (len) { |
| CFMutableDictionaryRef attrs = __CFAttributedStringCreateAttributesDictionary(alloc, NULL); |
| CFRunArrayInsert(newAttrStr->attributeArray, CFRangeMake(0, len), attrs); |
| CFRelease(attrs); |
| } |
| |
| // Now set the various ranges |
| CFIndex cnt; |
| for (cnt = 0; cnt < numRuns; cnt++) { |
| CFMutableDictionaryRef attrs = __CFAttributedStringCreateAttributesDictionary(alloc, attrDictionaries[cnt]); |
| __CFAssertRangeIsWithinLength(len, runRanges[cnt].location, runRanges[cnt].length); |
| CFRunArrayReplace(newAttrStr->attributeArray, runRanges[cnt], attrs, runRanges[cnt].length); |
| CFRelease(attrs); |
| } |
| |
| __CFAttributedStringSetMutable(newAttrStr, false); // Make it immutable |
| |
| return (CFAttributedStringRef)newAttrStr; |
| } |
| |
| |
| CFMutableAttributedStringRef CFAttributedStringCreateMutable(CFAllocatorRef alloc, CFIndex maxLength) { |
| CFIndex size = sizeof(struct __CFAttributedString) - sizeof(CFRuntimeBase); |
| struct __CFAttributedString *newAttrStr = (CFMutableAttributedStringRef)_CFRuntimeCreateInstance(alloc, CFAttributedStringGetTypeID(), size, NULL); |
| |
| if (newAttrStr == NULL) return NULL; |
| |
| newAttrStr->string = CFStringCreateMutable(alloc, maxLength); |
| newAttrStr->attributeArray = CFRunArrayCreate(alloc); |
| |
| return (CFMutableAttributedStringRef)newAttrStr; |
| |
| } |
| |
| CFMutableAttributedStringRef CFAttributedStringCreateMutableCopy(CFAllocatorRef alloc, CFIndex maxLength, CFAttributedStringRef attrStr) { |
| // CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), CFMutableAttributedStringRef, (NSAttributedString *)attrStr, mutableCopy); |
| |
| __CFAssertIsAttributedString(attrStr); |
| |
| // !!! Need to deal with maxLength |
| return __CFAttributedStringCreateMutableWithSubstring(alloc, attrStr, CFRangeMake(0, CFStringGetLength(attrStr->string))); |
| } |
| |
| |
| |
| /*** CFAttributedString functionality ***/ |
| |
| CFStringRef CFAttributedStringGetString(CFAttributedStringRef attrStr) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), CFStringRef, (NSAttributedString *)attrStr, string); |
| __CFAssertIsAttributedString(attrStr); |
| |
| return attrStr->string; |
| } |
| |
| CFIndex CFAttributedStringGetLength(CFAttributedStringRef attrStr) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), CFIndex, (NSAttributedString *)attrStr, length); |
| __CFAssertIsAttributedString(attrStr); |
| |
| return CFStringGetLength(attrStr->string); |
| } |
| |
| CFDictionaryRef CFAttributedStringGetAttributes(CFAttributedStringRef attrStr, CFIndex loc, CFRange *effectiveRange) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), CFDictionaryRef, (NSAttributedString *)attrStr, attributesAtIndex:(NSUInteger)loc effectiveRange:(NSRange *)effectiveRange); |
| __CFAssertIsAttributedString(attrStr); |
| __CFAssertIndexIsInBounds(attrStr, loc); |
| |
| return (CFDictionaryRef)CFRunArrayGetValueAtIndex(attrStr->attributeArray, loc, effectiveRange, NULL); |
| } |
| |
| CFTypeRef CFAttributedStringGetAttribute(CFAttributedStringRef attrStr, CFIndex loc, CFStringRef attrName, CFRange *effectiveRange) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), CFTypeRef, (NSAttributedString *)attrStr, attribute:(NSString *)attrName atIndex:(NSUInteger)loc effectiveRange:(NSRange *)effectiveRange); |
| __CFAssertIsAttributedString(attrStr); |
| __CFAssertIndexIsInBounds(attrStr, loc); |
| |
| CFDictionaryRef attrs = (CFDictionaryRef)CFRunArrayGetValueAtIndex(attrStr->attributeArray, loc, effectiveRange, NULL); |
| return CFDictionaryGetValue(attrs, attrName); |
| } |
| |
| CFDictionaryRef CFAttributedStringGetAttributesAndLongestEffectiveRange(CFAttributedStringRef attrStr, CFIndex location, CFRange rangeLimit, CFRange *longestEffectiveRange) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), CFDictionaryRef, (NSAttributedString *)attrStr, attributesAtIndex:(NSUInteger)location longestEffectiveRange:(NSRange *)longestEffectiveRange inRange:NSMakeRange(rangeLimit.location, rangeLimit.length)); |
| __CFAssertIsAttributedString(attrStr); |
| __CFAssertRangeIsInBounds(attrStr, rangeLimit.location, rangeLimit.length); |
| __CFAssertIndexIsInBounds(attrStr, location); |
| |
| CFDictionaryRef attrs = CFAttributedStringGetAttributes(attrStr, location, longestEffectiveRange); |
| |
| if (longestEffectiveRange) { // No need to do any of this if the range isn't desired |
| CFIndex leftEnd, rightEnd; |
| CFDictionaryRef other; |
| CFRange tmpRange; |
| |
| // First go right and set rightEnd pointing past the end |
| tmpRange = *longestEffectiveRange; |
| do { |
| if ((rightEnd = tmpRange.location + tmpRange.length) >= rangeLimit.location + rangeLimit.length) break; |
| other = CFAttributedStringGetAttributes(attrStr, rightEnd, &tmpRange); |
| } while (CFEqual(other, attrs)); |
| |
| // Now go back and set leftEnd |
| tmpRange = *longestEffectiveRange; |
| do { |
| if ((leftEnd = tmpRange.location) <= rangeLimit.location) break; |
| other = CFAttributedStringGetAttributes(attrStr, leftEnd - 1, &tmpRange); |
| } while (CFEqual(other, attrs)); |
| |
| // This basically intersects the rangeLimit with leftEnd,rightEnd |
| longestEffectiveRange->location = (leftEnd < rangeLimit.location) ? rangeLimit.location : leftEnd; |
| CFIndex rangeEnd = rangeLimit.location + rangeLimit.length; |
| longestEffectiveRange->length = ((rangeEnd < rightEnd) ? rangeEnd : rightEnd) - longestEffectiveRange->location; |
| } |
| return attrs; |
| } |
| |
| CFTypeRef CFAttributedStringGetAttributeAndLongestEffectiveRange(CFAttributedStringRef attrStr, CFIndex location, CFStringRef attrName, CFRange rangeLimit, CFRange *longestEffectiveRange) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), CFTypeRef, (NSAttributedString *)attrStr, attribute:(NSString *)attrName atIndex:(NSUInteger)location longestEffectiveRange:(NSRange *)longestEffectiveRange inRange: NSMakeRange(rangeLimit.location, rangeLimit.length)); |
| __CFAssertIsAttributedString(attrStr); |
| __CFAssertRangeIsInBounds(attrStr, rangeLimit.location, rangeLimit.length); |
| __CFAssertIndexIsInBounds(attrStr, location); |
| |
| CFTypeRef attr = CFAttributedStringGetAttribute(attrStr, location, attrName, longestEffectiveRange); |
| |
| if (longestEffectiveRange) { // No need to do any of this if the range isn't desired |
| CFIndex leftEnd, rightEnd; |
| CFTypeRef other; |
| CFRange tmpRange; |
| |
| // First go right and set rightEnd pointing past the end |
| tmpRange = *longestEffectiveRange; |
| do { |
| if ((rightEnd = tmpRange.location + tmpRange.length) >= rangeLimit.location + rangeLimit.length) break; |
| other = CFAttributedStringGetAttribute(attrStr, rightEnd, attrName, &tmpRange); |
| } while ((other == attr) || (other && attr && CFEqual(other, attr))); // The first check is to catch nil as well |
| |
| // Now go back and set leftEnd |
| tmpRange = *longestEffectiveRange; |
| do { |
| if ((leftEnd = tmpRange.location) <= rangeLimit.location) break; |
| other = CFAttributedStringGetAttribute(attrStr, leftEnd - 1, attrName, &tmpRange); |
| } while ((other == attr) || (other && attr && CFEqual(other, attr))); // The first check is to catch nil as well |
| |
| // This basically intersects the rangeLimit with leftEnd,rightEnd |
| longestEffectiveRange->location = (leftEnd < rangeLimit.location) ? rangeLimit.location : leftEnd; |
| CFIndex rangeEnd = rangeLimit.location + rangeLimit.length; |
| longestEffectiveRange->length = ((rangeEnd < rightEnd) ? rangeEnd : rightEnd) - longestEffectiveRange->location; |
| } |
| return attr; |
| } |
| |
| |
| /* The next two functions are SPI which allow return bulk information about attributes. See CFAttributedStringPriv.h for info. |
| */ |
| CFIndex _CFAttributedStringGetNumberOfRuns(CFAttributedStringRef attrStr, Boolean includeEmpty) { |
| CFIndex numRuns = 0; |
| CFRange remainingRange = CFRangeMake(0, CFAttributedStringGetLength(attrStr)); |
| while (remainingRange.length > 0) { |
| CFRange thisRange; |
| CFDictionaryRef dict = CFAttributedStringGetAttributesAndLongestEffectiveRange(attrStr, remainingRange.location, remainingRange, &thisRange); |
| if (includeEmpty || (CFDictionaryGetCount(dict) > 0)) numRuns++; |
| remainingRange.length -= thisRange.length; |
| remainingRange.location += thisRange.length; |
| } |
| return numRuns; |
| } |
| |
| void _CFAttributedStringGetRuns(CFAttributedStringRef attrStr, Boolean includeEmpty, CFDictionaryRef *attrDictionaries, CFRange *runRanges) { |
| CFRange remainingRange = CFRangeMake(0, CFAttributedStringGetLength(attrStr)); |
| while (remainingRange.length > 0) { |
| CFRange thisRange; |
| CFDictionaryRef dict = CFAttributedStringGetAttributesAndLongestEffectiveRange(attrStr, remainingRange.location, remainingRange, &thisRange); |
| if (includeEmpty || (CFDictionaryGetCount(dict) > 0)) { |
| if (attrDictionaries) *attrDictionaries++ = dict; |
| if (runRanges) *runRanges++ = thisRange; |
| } |
| remainingRange.length -= thisRange.length; |
| remainingRange.location += thisRange.length; |
| } |
| } |
| |
| |
| /*** CFMutableAttributedString functionality ***/ |
| |
| static void __CFDictionaryAddMultiple(CFMutableDictionaryRef dict, CFTypeRef *keys, CFTypeRef *values, CFIndex numAdditionalItems) { |
| CFIndex cnt; |
| for (cnt = 0; cnt < numAdditionalItems; cnt++) CFDictionarySetValue(dict, keys[cnt], values[cnt]); |
| } |
| |
| /* ??? This is not properly implemented at this point! We need to return a proxy |
| */ |
| CFMutableStringRef CFAttributedStringGetMutableString(CFMutableAttributedStringRef attrStr) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), CFMutableStringRef, (NSMutableAttributedString *)attrStr, mutableString); |
| return NULL; /* (CFMutableStringRef)(attrStr->string); */ |
| } |
| |
| void CFAttributedStringReplaceString(CFMutableAttributedStringRef attrStr, CFRange range, CFStringRef replacement) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), void, (NSMutableAttributedString *)attrStr, replaceCharactersInRange:NSMakeRange(range.location, range.length) withString:(NSString *)replacement); |
| __CFAssertIsAttributedStringAndMutable(attrStr); |
| __CFAssertRangeIsInBounds(attrStr, range.location, range.length); |
| |
| CFIndex replacementLen = CFStringGetLength(replacement); |
| CFMutableDictionaryRef attributesToBeUsed = NULL; |
| if (replacementLen > 0) { |
| // By default extend replaced attributes, or take them from the previous character |
| if (range.length > 0) { |
| attributesToBeUsed = (CFMutableDictionaryRef)CFRunArrayGetValueAtIndex(attrStr->attributeArray, range.location, NULL, NULL); |
| CFRetain(attributesToBeUsed); |
| } else if (range.location > 0) { |
| attributesToBeUsed = (CFMutableDictionaryRef)CFRunArrayGetValueAtIndex(attrStr->attributeArray, range.location - 1, NULL, NULL); |
| CFRetain(attributesToBeUsed); |
| } else if (CFStringGetLength(attrStr->string) > 0) { |
| attributesToBeUsed = (CFMutableDictionaryRef)CFRunArrayGetValueAtIndex(attrStr->attributeArray, 0, NULL, NULL); |
| CFRetain(attributesToBeUsed); |
| } else { |
| attributesToBeUsed = __CFAttributedStringCreateAttributesDictionary(CFGetAllocator(attrStr), NULL); |
| } |
| } |
| if (range.length > 0) { |
| CFRunArrayReplace(attrStr->attributeArray, range, attributesToBeUsed, replacementLen); |
| } else if (replacementLen) { |
| CFRunArrayInsert(attrStr->attributeArray, CFRangeMake(range.location, replacementLen), attributesToBeUsed); |
| } |
| if (attributesToBeUsed) CFRelease(attributesToBeUsed); |
| CFStringReplace((CFMutableStringRef)(attrStr->string), range, replacement); |
| // !!! [self edited:NSAttributedStringEditedCharacters range:range changeInLength:replacementLen - range.length]; |
| } |
| |
| void CFAttributedStringSetAttributes(CFMutableAttributedStringRef attrStr, CFRange range, CFDictionaryRef replacementAttrs, Boolean clearOtherAttributes) { |
| if (clearOtherAttributes) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), void, (NSMutableAttributedString *)attrStr, setAttributes:(NSDictionary *)replacementAttrs range: NSMakeRange(range.location, range.length)); |
| } else { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), void, (NSMutableAttributedString *)attrStr, addAttributes:(NSDictionary *)replacementAttrs range: NSMakeRange(range.location, range.length)); |
| } |
| |
| __CFAssertIsAttributedStringAndMutable(attrStr); |
| __CFAssertRangeIsInBounds(attrStr, range.location, range.length); |
| |
| if (clearOtherAttributes) { // Just blast all attribute dictionaries in the specified range |
| if (range.length) { |
| CFMutableDictionaryRef attrs = __CFAttributedStringCreateAttributesDictionary(CFGetAllocator(attrStr), replacementAttrs); |
| CFRunArrayReplace(attrStr->attributeArray, range, attrs, range.length); |
| CFRelease(attrs); |
| // !!! [self edited:NSAttributedStringEditedAttributes range:range changeInLength:0]; |
| } |
| } else { // More difficult --- set specified keys and values on the existing dictionaries in the specified range |
| CFIndex numAdditionalItems = CFDictionaryGetCount(replacementAttrs); |
| if (numAdditionalItems) { |
| // Extract the new keys and values so we don't do it over and over for each range |
| createLocalArray(additionalKeys, numAdditionalItems); |
| createLocalArray(additionalValues, numAdditionalItems); |
| CFDictionaryGetKeysAndValues(replacementAttrs, additionalKeys, additionalValues); |
| |
| // CFAttributedStringBeginEditing(attrStr); |
| while (range.length) { |
| CFRange effectiveRange; |
| CFMutableDictionaryRef attrs = (CFMutableDictionaryRef)CFRunArrayGetValueAtIndex(attrStr->attributeArray, range.location, &effectiveRange, NULL); |
| // Intersect effectiveRange and range |
| if (effectiveRange.location < range.location) { |
| effectiveRange.length -= (range.location - effectiveRange.location); |
| effectiveRange.location = range.location; |
| } |
| if (effectiveRange.length > range.length) effectiveRange.length = range.length; |
| // We need to make a new copy |
| attrs = __CFAttributedStringCreateAttributesDictionary(CFGetAllocator(attrStr), attrs); |
| __CFDictionaryAddMultiple(attrs, additionalKeys, additionalValues, numAdditionalItems); |
| CFRunArrayReplace(attrStr->attributeArray, effectiveRange, attrs, effectiveRange.length); |
| CFRelease(attrs); |
| range.length -= effectiveRange.length; |
| range.location += effectiveRange.length; |
| } |
| // CFAttributedStringEndEditing(attrStr); |
| |
| freeLocalArray(additionalKeys); |
| freeLocalArray(additionalValues); |
| } |
| } |
| } |
| |
| void CFAttributedStringSetAttribute(CFMutableAttributedStringRef attrStr, CFRange range, CFStringRef attrName, CFTypeRef value) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), void, (NSMutableAttributedString *)attrStr, addAttribute:(NSString *)attrName value:(id) value range:NSMakeRange(range.location, range.length)); |
| __CFAssertIsAttributedStringAndMutable(attrStr); |
| __CFAssertRangeIsInBounds(attrStr, range.location, range.length); |
| |
| // CFAttributedStringBeginEditing(attrStr); |
| while (range.length) { |
| CFRange effectiveRange; |
| // effectiveRange.location returned here may be equal to or smaller than range.location |
| CFMutableDictionaryRef attrs = (CFMutableDictionaryRef)CFRunArrayGetValueAtIndex(attrStr->attributeArray, range.location, &effectiveRange, NULL); |
| // Intersect effectiveRange and range |
| if (effectiveRange.location < range.location) { |
| effectiveRange.length -= (range.location - effectiveRange.location); |
| effectiveRange.location = range.location; |
| } |
| if (effectiveRange.length > range.length) effectiveRange.length = range.length; |
| // First check to see if the same value already exists; this will avoid a copy |
| CFTypeRef existingValue = CFDictionaryGetValue(attrs, attrName); |
| if (!existingValue || !CFEqual(existingValue, value)) { |
| // We need to make a new copy |
| attrs = __CFAttributedStringCreateAttributesDictionary(CFGetAllocator(attrStr), attrs); |
| CFDictionarySetValue(attrs, attrName, value); |
| CFRunArrayReplace(attrStr->attributeArray, effectiveRange, attrs, effectiveRange.length); |
| CFRelease(attrs); |
| } |
| range.length -= effectiveRange.length; |
| range.location += effectiveRange.length; |
| } |
| // CFAttributedStringEndEditing(attrStr); |
| } |
| |
| void CFAttributedStringRemoveAttribute(CFMutableAttributedStringRef attrStr, CFRange range, CFStringRef attrName) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), void, (NSMutableAttributedString *)attrStr, removeAttribute:(NSString *)attrName range:NSMakeRange(range.location, range.length)); |
| __CFAssertIsAttributedStringAndMutable(attrStr); |
| __CFAssertRangeIsInBounds(attrStr, range.location, range.length); |
| |
| // CFAttributedStringBeginEditing(attrStr); |
| while (range.length) { |
| CFRange effectiveRange; |
| CFMutableDictionaryRef attrs = (CFMutableDictionaryRef)CFRunArrayGetValueAtIndex(attrStr->attributeArray, range.location, &effectiveRange, NULL); |
| // Intersect effectiveRange and range |
| if (effectiveRange.location < range.location) { |
| effectiveRange.length -= (range.location - effectiveRange.location); |
| effectiveRange.location = range.location; |
| } |
| if (effectiveRange.length > range.length) effectiveRange.length = range.length; |
| // First check to see if the value is not there; this will avoid a copy |
| if (CFDictionaryContainsKey(attrs, attrName)) { |
| // We need to make a new copy |
| attrs = __CFAttributedStringCreateAttributesDictionary(CFGetAllocator(attrStr), attrs); |
| CFDictionaryRemoveValue(attrs, attrName); |
| CFRunArrayReplace(attrStr->attributeArray, effectiveRange, attrs, effectiveRange.length); |
| CFRelease(attrs); |
| } |
| range.length -= effectiveRange.length; |
| range.location += effectiveRange.length; |
| } |
| // CFAttributedStringEndEditing(attrStr); |
| } |
| |
| void CFAttributedStringReplaceAttributedString(CFMutableAttributedStringRef attrStr, CFRange range, CFAttributedStringRef replacement) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), void, (NSMutableAttributedString *)attrStr, replaceCharactersInRange:NSMakeRange(range.location, range.length) withAttributedString:(NSAttributedString *)replacement); |
| __CFAssertIsAttributedStringAndMutable(attrStr); |
| __CFAssertRangeIsInBounds(attrStr, range.location, range.length); |
| |
| CFStringRef otherStr = CFAttributedStringGetString(replacement); |
| CFIndex stringLen = CFStringGetLength(otherStr); |
| |
| if (stringLen > 0) { |
| CFAllocatorRef allocator = CFGetAllocator(attrStr); |
| CFRange attrRange = {0, 0}; |
| while (attrRange.location < stringLen) { |
| CFDictionaryRef otherAttrs = CFAttributedStringGetAttributes(replacement, attrRange.location, &attrRange); |
| CFMutableDictionaryRef attrs = __CFAttributedStringCreateAttributesDictionary(allocator, otherAttrs); |
| CFRunArrayInsert(attrStr->attributeArray, CFRangeMake(attrRange.location + range.location, attrRange.length), attrs); |
| CFRelease(attrs); |
| attrRange.location += attrRange.length; |
| } |
| } |
| if (range.length > 0) CFRunArrayDelete(attrStr->attributeArray, CFRangeMake(range.location + stringLen, range.length)); |
| CFStringReplace((CFMutableStringRef)(attrStr->string), range, otherStr); |
| // [self edited:NSAttributedStringEditedCharacters|NSAttributedStringEditedAttributes range:range changeInLength:stringLen - range.length]; |
| } |
| |
| void CFAttributedStringBeginEditing(CFMutableAttributedStringRef attrStr) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), void, (NSMutableAttributedString *)attrStr, beginEditing); |
| } |
| |
| void CFAttributedStringEndEditing(CFMutableAttributedStringRef attrStr) { |
| CF_OBJC_FUNCDISPATCHV(CFAttributedStringGetTypeID(), void, (NSMutableAttributedString *)attrStr, endEditing); |
| } |
| |
| |
| |
| /*** Functions for NSCFAttributedString usage ***/ |
| |
| CFIndex _CFAttributedStringGetLength(CFAttributedStringRef attrStr) { |
| return CFStringGetLength(attrStr->string); |
| } |
| |
| |
| int _CFAttributedStringCheckAndReplace(CFMutableAttributedStringRef attrStr, CFRange range, CFStringRef replacement) { |
| CFIndex len = CFStringGetLength(attrStr->string); |
| if (range.location > len || (range.location + range.length > len)) return _CFStringErrBounds; |
| if (!__CFAttributedStringIsMutable(attrStr)) return _CFStringErrNotMutable; |
| CFAttributedStringReplaceString(attrStr, range, replacement); // ??? Do this faster! |
| return _CFStringErrNone; |
| } |
| |
| int _CFAttributedStringCheckAndReplaceAttributed(CFMutableAttributedStringRef attrStr, CFRange range, CFAttributedStringRef replacement) { |
| CFIndex len = CFStringGetLength(attrStr->string); |
| if (range.location > len || (range.location + range.length > len)) return _CFStringErrBounds; |
| if (!__CFAttributedStringIsMutable(attrStr)) return _CFStringErrNotMutable; |
| if (replacement) { // Special: NULL means delete |
| CFAttributedStringReplaceAttributedString(attrStr, range, replacement); // ??? Do this faster! |
| } else { |
| CFAttributedStringReplaceString(attrStr, range, CFSTR("")); |
| } |
| return _CFStringErrNone; |
| } |
| |
| int _CFAttributedStringCheckAndSetAttributes(CFMutableAttributedStringRef attrStr, CFRange range, CFTypeRef attrOrAttrs, Boolean clearOthers) { |
| CFIndex len = CFStringGetLength(attrStr->string); |
| if (range.location > len || (range.location + range.length > len)) return _CFStringErrBounds; |
| if (!__CFAttributedStringIsMutable(attrStr)) return _CFStringErrNotMutable; |
| CFAttributedStringSetAttributes(attrStr, range, (CFDictionaryRef)attrOrAttrs, clearOthers); // ??? Do this faster! |
| return _CFStringErrNone; |
| } |
| |
| int _CFAttributedStringCheckAndSetAttribute(CFMutableAttributedStringRef attrStr, CFRange range, CFStringRef attrName, CFTypeRef attr) { |
| CFIndex len = CFStringGetLength(attrStr->string); |
| if (range.location > len || (range.location + range.length > len)) return _CFStringErrBounds; |
| if (!__CFAttributedStringIsMutable(attrStr)) return _CFStringErrNotMutable; |
| if (!attrName) return _CFStringErrNilArg; |
| if (attr) { |
| CFAttributedStringSetAttribute(attrStr, range, attrName, attr); // ??? Do this faster! |
| } else { |
| CFAttributedStringRemoveAttribute(attrStr, range, attrName); // ??? Do this faster! |
| } |
| return _CFStringErrNone; |
| } |
| |
| |
| |