blob: 064e2a51bffd14126b1cd9c51e1324e2d90a53fa [file] [log] [blame]
/* CFBundle_DebugStrings.c
Copyright (c) 1999-2017, Apple Inc. and the Swift project authors
Portions Copyright (c) 2014-2017, 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
Responsibility: Sean Stewart <sean_stewart@apple.com>
*/
#include <assert.h>
#include "CFBundle_Internal.h"
// Directionality formatting codepoints
#define UTF16_RIGHT_TO_LEFT_OVERRIDE 0x202e
#define UTF16_POP_DIRECTIONAL_FORMATTING 0x202c
// Combining accent codepoints
#define UTF16_COMBINING_GRAVE_ACCENT 0x0300
#define UTF16_COMBINING_CEDILLA 0x0327
#define UTF16_COMBINING_TILDE 0x0303
#define UTF16_COMBINING_RING_BELOW 0x0325
#define UTF16_COMBINING_RING_ABOVE 0x030A
#define UTF16_COMBINING_DIAERESIS 0x0308
CF_EXPORT CFStringRef _CFDoubledStringCreate(CFStringRef theString) {
CFMutableStringRef _Nonnull doubledString = CFStringCreateMutable(kCFAllocatorSystemDefault, 0);
CFStringAppend(doubledString, theString);
// Prevent doubling any format string specifiers that may be present
CFStringFindAndReplace(doubledString, CFSTR("%"), CFSTR(""), CFRangeMake(0, CFStringGetLength(doubledString)), 0);
CFStringAppendFormat(doubledString, NULL, CFSTR(" %@"), theString);
return (CFStringRef)doubledString;
}
static CFStringRef __CFAccentuatedStringCreateWithAcceptableAccentChars(CFStringRef inString, UniChar const *acceptableAccentChars, CFIndex acceptableCharLength) {
// Copy the incoming string
CFMutableStringRef _Nonnull workingString = CFStringCreateMutableCopy(kCFAllocatorDefault, CFStringGetLength(inString), inString);
// Create UniChar (UTF-16) buffer from original string
CFIndex const workingStringLength = CFStringGetLength(workingString);
CFStringInlineBuffer originalStringInlineBuffer;
CFStringInitInlineBuffer(workingString, &originalStringInlineBuffer, CFRangeMake(0, workingStringLength));
// Create new double-length UniChar (UTF-16) buffer for the new string
// We will trim the size later, but worst case is that there will be no skipped whitespace characters
UniChar *_Nonnull const accentedStringBuffer = malloc((workingStringLength * 2 + 1) * sizeof(UniChar)); // +1 for null char
CFIndex accentedStringCursor = 0;
// Create CFCharacterSets that contain characters we wish to skip over during the accentuation loop
static CFCharacterSetRef _Nullable charsToSkip = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CFMutableCharacterSetRef _Nonnull charsToSkipMutable = CFCharacterSetCreateMutable(kCFAllocatorDefault);
CFCharacterSetUnion(charsToSkipMutable, CFCharacterSetGetPredefined(kCFCharacterSetControl));
CFCharacterSetUnion(charsToSkipMutable, CFCharacterSetGetPredefined(kCFCharacterSetWhitespaceAndNewline));
CFCharacterSetUnion(charsToSkipMutable, CFCharacterSetGetPredefined(kCFCharacterSetPunctuation));
CFCharacterSetUnion(charsToSkipMutable, CFCharacterSetGetPredefined(kCFCharacterSetSymbol));
CFCharacterSetUnion(charsToSkipMutable, CFCharacterSetGetPredefined(kCFCharacterSetDecimalDigit));
CFCharacterSetUnion(charsToSkipMutable, CFCharacterSetGetPredefined(kCFCharacterSetNonBase));
charsToSkip = CFCharacterSetCreateCopy(kCFAllocatorDefault, charsToSkipMutable);
CFRelease(charsToSkipMutable);
});
// For each non-skipped character, add a combining accent after the codepoint
for (CFIndex i = 0; i < workingStringLength;) { // inner loop increments i
CFRange const composedCharRange = CFStringGetRangeOfComposedCharactersAtIndex(workingString, i); // range length will always be >=1
UniChar c;
// Enqueue the characters within the composedCharRange
for (CFIndex j = 0; j < composedCharRange.length; j++) {
c = CFStringGetCharacterFromInlineBuffer(&originalStringInlineBuffer, i++);
// Defend against CFStringGetCharacterFromInlineBuffer edge case listed in documentation
if (c == 0) assert("UniChar at specified index could not be accessed as it is outside the original range specified when initializing the CFStringInlineBuffer");
accentedStringBuffer[accentedStringCursor++] = c;
}
// Don't sprinkle accents onto composed char sequences and characters belonging to certain ranges
// The goal here is to _never_ ever accentuate composed char sequences (like emoji)
if ((composedCharRange.length == 1) && !CFCharacterSetIsCharacterMember(charsToSkip, c)) {
// Deterministically pair a combining accent
CFIndex const combiningAccentIndex = c % acceptableCharLength;
UniChar const accentChar = acceptableAccentChars[combiningAccentIndex];
accentedStringBuffer[accentedStringCursor++] = accentChar;
}
}
// Terminate the UniChar buffer with a null char
accentedStringBuffer[accentedStringCursor] = (UniChar)0x0;
// Create a mutable string ref from the new char buffer (dealloc using kCFAllocatorNull and free the char buffer later ourselves)
CFMutableStringRef _Nonnull const accentedStringFromCharBuffer = CFStringCreateMutableWithExternalCharactersNoCopy(NULL, accentedStringBuffer, accentedStringCursor, accentedStringCursor, kCFAllocatorNull);
// Make a copy of the string so that CFString can optimize itself
// CFStrings bound to client-owned buffers do not benefit from CFString-internal optimizations
CFStringRef _Nonnull accentedString = CFStringCreateCopy(kCFAllocatorDefault, accentedStringFromCharBuffer);
// Cleanup
CFRelease(workingString);
CFRelease(accentedStringFromCharBuffer);
free(accentedStringBuffer);
return (CFStringRef)accentedString;
}
CF_EXPORT CFStringRef _CFAccentuatedStringCreate(CFStringRef theString) {
/* Default set of "acceptable" Unicode combining accent codepoints
* These are hand-selected from the superset of combining accents because this subset is commonly used on Latin characters
* Additionally they're visually obvious and all look unique (that's why we choose to use only the GRAVE and not GRAVE & ACUTE accents due to similarity)
*/
static UniChar const acceptableAccentChars[] = {
UTF16_COMBINING_GRAVE_ACCENT,
UTF16_COMBINING_CEDILLA,
UTF16_COMBINING_TILDE,
UTF16_COMBINING_RING_BELOW,
UTF16_COMBINING_RING_ABOVE,
UTF16_COMBINING_DIAERESIS
};
static CFIndex const len = sizeof(acceptableAccentChars)/sizeof(UniChar);
return __CFAccentuatedStringCreateWithAcceptableAccentChars(theString, acceptableAccentChars, len);
}
CF_EXPORT CFStringRef _CFAffixedStringCreate(CFStringRef theString, CFStringRef prefix, CFStringRef suffix) {
return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@%@"), prefix, theString, suffix);
}
CF_EXPORT CFStringRef _CFRLORightToLeftStringCreate(CFStringRef theString) {
CFStringRef rtlString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%C%@%C"), UTF16_RIGHT_TO_LEFT_OVERRIDE, theString, UTF16_POP_DIRECTIONAL_FORMATTING);
return rtlString;
}