mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
332 lines
15 KiB
Plaintext
332 lines
15 KiB
Plaintext
//
|
|
// ASTextKitCoreTextAdditions.mm
|
|
// Texture
|
|
//
|
|
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
|
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
|
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
|
|
#import "ASTextKitCoreTextAdditions.h"
|
|
|
|
#if AS_ENABLE_TEXTNODE
|
|
|
|
#import <CoreText/CTFont.h>
|
|
#import <CoreText/CTStringAttributes.h>
|
|
|
|
#import <AsyncDisplayKit/ASAssert.h>
|
|
|
|
#pragma mark - Public
|
|
BOOL ASAttributeWithNameIsUnsupportedCoreTextAttribute(NSString *attributeName)
|
|
{
|
|
static NSSet *coreTextAttributes;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
coreTextAttributes = [NSSet setWithObjects:(__bridge id)kCTForegroundColorAttributeName,
|
|
kCTForegroundColorFromContextAttributeName,
|
|
kCTForegroundColorAttributeName,
|
|
kCTStrokeColorAttributeName,
|
|
kCTUnderlineStyleAttributeName,
|
|
kCTVerticalFormsAttributeName,
|
|
kCTRunDelegateAttributeName,
|
|
kCTBaselineClassAttributeName,
|
|
kCTBaselineInfoAttributeName,
|
|
kCTBaselineReferenceInfoAttributeName,
|
|
kCTUnderlineColorAttributeName,
|
|
kCTParagraphStyleAttributeName,
|
|
nil];
|
|
});
|
|
return [coreTextAttributes containsObject:attributeName];
|
|
}
|
|
|
|
NSDictionary *NSAttributedStringAttributesForCoreTextAttributes(NSDictionary *coreTextAttributes)
|
|
{
|
|
NSMutableDictionary *cleanAttributes = [[NSMutableDictionary alloc] initWithCapacity:coreTextAttributes.count];
|
|
|
|
[coreTextAttributes enumerateKeysAndObjectsUsingBlock:^(NSString *coreTextKey, id coreTextValue, BOOL *stop) {
|
|
// The following attributes are not supported on NSAttributedString. Should they become available, we should add them.
|
|
/*
|
|
kCTForegroundColorFromContextAttributeName
|
|
kCTSuperscriptAttributeName
|
|
kCTGlyphInfoAttributeName
|
|
kCTCharacterShapeAttributeName
|
|
kCTLanguageAttributeName
|
|
kCTRunDelegateAttributeName
|
|
kCTBaselineClassAttributeName
|
|
kCTBaselineInfoAttributeName
|
|
kCTBaselineReferenceInfoAttributeName
|
|
kCTWritingDirectionAttributeName
|
|
kCTUnderlineColorAttributeName
|
|
*/
|
|
|
|
// Conversely, the following attributes are not supported on CFAttributedString. Should they become available, we should add them.
|
|
/*
|
|
NSStrikethroughStyleAttributeName
|
|
NSShadowAttributeName
|
|
NSBackgroundColorAttributeName
|
|
*/
|
|
|
|
// kCTFontAttributeName -> NSFontAttributeName
|
|
if ([coreTextKey isEqualToString:(NSString *)kCTFontAttributeName]) {
|
|
CTFontRef coreTextFont = (__bridge CTFontRef)coreTextValue;
|
|
cleanAttributes[NSFontAttributeName] = (__bridge UIFont *)coreTextFont;
|
|
}
|
|
// kCTKernAttributeName -> NSKernAttributeName
|
|
else if ([coreTextKey isEqualToString:(NSString *)kCTKernAttributeName]) {
|
|
cleanAttributes[NSKernAttributeName] = (NSNumber *)coreTextValue;
|
|
}
|
|
// kCTLigatureAttributeName -> NSLigatureAttributeName
|
|
else if ([coreTextKey isEqualToString:(NSString *)kCTLigatureAttributeName]) {
|
|
cleanAttributes[NSLigatureAttributeName] = (NSNumber *)coreTextValue;
|
|
}
|
|
// kCTForegroundColorAttributeName -> NSForegroundColorAttributeName
|
|
else if ([coreTextKey isEqualToString:(NSString *)kCTForegroundColorAttributeName]) {
|
|
cleanAttributes[NSForegroundColorAttributeName] = [UIColor colorWithCGColor:(CGColorRef)coreTextValue];
|
|
}
|
|
// kCTParagraphStyleAttributeName -> NSParagraphStyleAttributeName
|
|
else if ([coreTextKey isEqualToString:(NSString *)kCTParagraphStyleAttributeName]) {
|
|
if ([coreTextValue isKindOfClass:[NSParagraphStyle class]]) {
|
|
cleanAttributes[NSParagraphStyleAttributeName] = (NSParagraphStyle *)coreTextValue;
|
|
}
|
|
else {
|
|
cleanAttributes[NSParagraphStyleAttributeName] = [NSParagraphStyle paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextValue];
|
|
}
|
|
}
|
|
// kCTStrokeWidthAttributeName -> NSStrokeWidthAttributeName
|
|
else if ([coreTextKey isEqualToString:(NSString *)kCTStrokeWidthAttributeName]) {
|
|
cleanAttributes[NSStrokeWidthAttributeName] = (NSNumber *)coreTextValue;
|
|
}
|
|
// kCTStrokeColorAttributeName -> NSStrokeColorAttributeName
|
|
else if ([coreTextKey isEqualToString:(NSString *)kCTStrokeColorAttributeName]) {
|
|
cleanAttributes[NSStrokeColorAttributeName] = [UIColor colorWithCGColor:(CGColorRef)coreTextValue];
|
|
}
|
|
// kCTUnderlineStyleAttributeName -> NSUnderlineStyleAttributeName
|
|
else if ([coreTextKey isEqualToString:(NSString *)kCTUnderlineStyleAttributeName]) {
|
|
cleanAttributes[NSUnderlineStyleAttributeName] = (NSNumber *)coreTextValue;
|
|
}
|
|
// kCTVerticalFormsAttributeName -> NSVerticalGlyphFormAttributeName
|
|
else if ([coreTextKey isEqualToString:(NSString *)kCTVerticalFormsAttributeName]) {
|
|
BOOL flag = (BOOL)CFBooleanGetValue((CFBooleanRef)coreTextValue);
|
|
cleanAttributes[NSVerticalGlyphFormAttributeName] = @((int)flag); // NSVerticalGlyphFormAttributeName is documented to be an NSNumber with an integer that's either 0 or 1.
|
|
}
|
|
// Don't filter out any internal text attributes
|
|
else if (!ASAttributeWithNameIsUnsupportedCoreTextAttribute(coreTextKey)){
|
|
cleanAttributes[coreTextKey] = coreTextValue;
|
|
}
|
|
}];
|
|
|
|
return cleanAttributes;
|
|
}
|
|
|
|
NSAttributedString *ASCleanseAttributedStringOfCoreTextAttributes(NSAttributedString *dirtyAttributedString)
|
|
{
|
|
if (!dirtyAttributedString)
|
|
return nil;
|
|
|
|
// First see if there are any core text attributes on the string
|
|
__block BOOL containsCoreTextAttributes = NO;
|
|
[dirtyAttributedString enumerateAttributesInRange:NSMakeRange(0, dirtyAttributedString.length)
|
|
options:0
|
|
usingBlock:^(NSDictionary *dirtyAttributes, NSRange range, BOOL *stop) {
|
|
[dirtyAttributes enumerateKeysAndObjectsUsingBlock:^(NSString *coreTextKey, id coreTextValue, BOOL *innerStop) {
|
|
if (ASAttributeWithNameIsUnsupportedCoreTextAttribute(coreTextKey)) {
|
|
containsCoreTextAttributes = YES;
|
|
*innerStop = YES;
|
|
}
|
|
}];
|
|
*stop = containsCoreTextAttributes;
|
|
}];
|
|
if (containsCoreTextAttributes) {
|
|
|
|
NSString *plainString = dirtyAttributedString.string;
|
|
NSMutableAttributedString *cleanAttributedString = [[NSMutableAttributedString alloc] initWithString:plainString];
|
|
|
|
// Iterate over all of the attributes, cleaning them as appropriate and applying them as we go.
|
|
[dirtyAttributedString enumerateAttributesInRange:NSMakeRange(0, plainString.length)
|
|
options:0
|
|
usingBlock:^(NSDictionary *dirtyAttributes, NSRange range, BOOL *stop) {
|
|
[cleanAttributedString addAttributes:NSAttributedStringAttributesForCoreTextAttributes(dirtyAttributes) range:range];
|
|
}];
|
|
|
|
return cleanAttributedString;
|
|
} else {
|
|
return [dirtyAttributedString copy];
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark -
|
|
@implementation NSParagraphStyle (ASTextKitCoreTextAdditions)
|
|
|
|
+ (NSParagraphStyle *)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextParagraphStyle NS_RETURNS_RETAINED
|
|
{
|
|
NSMutableParagraphStyle *newParagraphStyle = [[NSMutableParagraphStyle alloc] init];
|
|
|
|
if (!coreTextParagraphStyle) {
|
|
return newParagraphStyle;
|
|
}
|
|
|
|
// The following paragraph style specifiers are not supported on NSParagraphStyle. Should they become available, we should add them.
|
|
/*
|
|
kCTParagraphStyleSpecifierTabStops
|
|
kCTParagraphStyleSpecifierDefaultTabInterval
|
|
kCTParagraphStyleSpecifierMaximumLineSpacing
|
|
kCTParagraphStyleSpecifierMinimumLineSpacing
|
|
kCTParagraphStyleSpecifierLineSpacingAdjustment
|
|
kCTParagraphStyleSpecifierLineBoundsOptions
|
|
*/
|
|
|
|
// Conversely, the following paragraph styles are not supported on CTParagraphStyle. Should they become available, we should add them.
|
|
/*
|
|
hyphenationFactor
|
|
*/
|
|
|
|
// kCTParagraphStyleSpecifierAlignment -> alignment
|
|
CTTextAlignment coreTextAlignment;
|
|
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierAlignment,
|
|
sizeof(coreTextAlignment),
|
|
&coreTextAlignment)) {
|
|
newParagraphStyle.alignment = NSTextAlignmentFromCTTextAlignment(coreTextAlignment);
|
|
}
|
|
|
|
// kCTParagraphStyleSpecifierFirstLineHeadIndent -> firstLineHeadIndent
|
|
CGFloat firstLineHeadIndent;
|
|
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierFirstLineHeadIndent,
|
|
sizeof(firstLineHeadIndent),
|
|
&firstLineHeadIndent)) {
|
|
newParagraphStyle.firstLineHeadIndent = firstLineHeadIndent;
|
|
}
|
|
|
|
// kCTParagraphStyleSpecifierHeadIndent -> headIndent
|
|
CGFloat headIndent;
|
|
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierHeadIndent,
|
|
sizeof(headIndent),
|
|
&headIndent)) {
|
|
newParagraphStyle.headIndent = headIndent;
|
|
}
|
|
|
|
// kCTParagraphStyleSpecifierTailIndent -> tailIndent
|
|
CGFloat tailIndent;
|
|
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierTailIndent,
|
|
sizeof(tailIndent),
|
|
&tailIndent)) {
|
|
newParagraphStyle.tailIndent = tailIndent;
|
|
}
|
|
|
|
// kCTParagraphStyleSpecifierLineBreakMode -> lineBreakMode
|
|
CTLineBreakMode coreTextLineBreakMode;
|
|
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierLineBreakMode,
|
|
sizeof(coreTextLineBreakMode),
|
|
&coreTextLineBreakMode)) {
|
|
newParagraphStyle.lineBreakMode = (NSLineBreakMode)coreTextLineBreakMode; // They're the same enum.
|
|
}
|
|
|
|
// kCTParagraphStyleSpecifierLineHeightMultiple -> lineHeightMultiple
|
|
CGFloat lineHeightMultiple;
|
|
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierLineHeightMultiple,
|
|
sizeof(lineHeightMultiple),
|
|
&lineHeightMultiple)) {
|
|
newParagraphStyle.lineHeightMultiple = lineHeightMultiple;
|
|
}
|
|
|
|
// kCTParagraphStyleSpecifierMaximumLineHeight -> maximumLineHeight
|
|
CGFloat maximumLineHeight;
|
|
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierMaximumLineHeight,
|
|
sizeof(maximumLineHeight),
|
|
&maximumLineHeight)) {
|
|
newParagraphStyle.maximumLineHeight = maximumLineHeight;
|
|
}
|
|
|
|
// kCTParagraphStyleSpecifierMinimumLineHeight -> minimumLineHeight
|
|
CGFloat minimumLineHeight;
|
|
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierMinimumLineHeight,
|
|
sizeof(minimumLineHeight),
|
|
&minimumLineHeight)) {
|
|
newParagraphStyle.minimumLineHeight = minimumLineHeight;
|
|
}
|
|
|
|
CGFloat lineSpacing = 0;
|
|
#if TARGET_OS_IOS
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
// kCTParagraphStyleSpecifierLineSpacing -> lineSpacing
|
|
// Note that kCTParagraphStyleSpecifierLineSpacing is deprecated and will die soon. We should not be using it.
|
|
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierLineSpacing,
|
|
sizeof(lineSpacing),
|
|
&lineSpacing)) {
|
|
newParagraphStyle.lineSpacing = lineSpacing;
|
|
}
|
|
#pragma clang diagnostic pop
|
|
#endif
|
|
|
|
// Attempt to weakly map the following onto -[NSParagraphStyle lineSpacing]:
|
|
// - kCTParagraphStyleSpecifierMinimumLineSpacing
|
|
// - kCTParagraphStyleSpecifierMaximumLineSpacing
|
|
// - kCTParagraphStyleSpecifierLineSpacingAdjustment
|
|
if (fabs(lineSpacing) <= FLT_EPSILON &&
|
|
CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierMinimumLineSpacing,
|
|
sizeof(lineSpacing),
|
|
&lineSpacing)) {
|
|
newParagraphStyle.lineSpacing = lineSpacing;
|
|
}
|
|
|
|
if (fabs(lineSpacing) <= FLT_EPSILON &&
|
|
CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierMaximumLineSpacing,
|
|
sizeof(lineSpacing),
|
|
&lineSpacing)) {
|
|
newParagraphStyle.lineSpacing = lineSpacing;
|
|
}
|
|
|
|
if (fabs(lineSpacing) <= FLT_EPSILON &&
|
|
CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierLineSpacingAdjustment,
|
|
sizeof(lineSpacing),
|
|
&lineSpacing)) {
|
|
newParagraphStyle.lineSpacing = lineSpacing;
|
|
}
|
|
|
|
// kCTParagraphStyleSpecifierParagraphSpacing -> paragraphSpacing
|
|
CGFloat paragraphSpacing;
|
|
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierParagraphSpacing,
|
|
sizeof(paragraphSpacing),
|
|
¶graphSpacing)) {
|
|
newParagraphStyle.paragraphSpacing = paragraphSpacing;
|
|
}
|
|
|
|
// kCTParagraphStyleSpecifierParagraphSpacingBefore -> paragraphSpacingBefore
|
|
CGFloat paragraphSpacingBefore;
|
|
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierParagraphSpacingBefore,
|
|
sizeof(paragraphSpacingBefore),
|
|
¶graphSpacingBefore)) {
|
|
newParagraphStyle.paragraphSpacingBefore = paragraphSpacingBefore;
|
|
}
|
|
|
|
// kCTParagraphStyleSpecifierBaseWritingDirection -> baseWritingDirection
|
|
CTWritingDirection coreTextBaseWritingDirection;
|
|
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle,
|
|
kCTParagraphStyleSpecifierBaseWritingDirection,
|
|
sizeof(coreTextBaseWritingDirection),
|
|
&coreTextBaseWritingDirection)) {
|
|
newParagraphStyle.baseWritingDirection = (NSWritingDirection)coreTextBaseWritingDirection; // They're the same enum.
|
|
}
|
|
|
|
return newParagraphStyle;
|
|
}
|
|
|
|
@end
|
|
|
|
#endif
|