text: // clang-format off
  
  // 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 "ForFoundationOnly.h"
  #include "CFInternal.h"
  
  #include <Foundation/NSMutableAttributedString.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;
  }
  
  // WINOBJC: This function is for Foundation's benefit; no one else should use it.
  CF_EXPORT Boolean _CFAttributedStringIsMutable(CFAttributedStringRef attrStr) { return __CFAttributedStringIsMutable(attrStr); }
  
  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(arrayName, count) \
      CFTypeRef arrayName ## Buf[localArrayStackSize];  \
      CFTypeRef *arrayName = (CFTypeRef *)((count <= localArrayStackSize) ? (arrayName ## Buf) : ((count < LONG_MAX / sizeof(CFTypeRef)) ? malloc(count * sizeof(CFTypeRef)) : NULL));
  
  #define freeLocalArray(arrayName) \
      if (arrayName != arrayName ## Buf) free(arrayName);
  
  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);
  // WINOBJC: still needed
      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);
  //  WINOBJC: still needed
      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;
  }
  
  // clang-format on
  