blob: 61306e8fa2d663bec7779837328130e858cb209a [file] [log] [blame]
// 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;
}