Swiftgram/Source/TextKit/ASTextKitCoreTextAdditions.m
Garrett Moon 8013e25524 Update license v2 (#67)
* Fixed license

* Update all licenses

* Update Dangerfile for new license

* Update already updated licenses

* Closer…

* Closer…

* Closer…

* Closer…

* Closer…

* Closer…

* Closer…

* Closer…

* Closer…

* Closer…

* Closer…
2017-04-24 16:59:57 -07:00

259 lines
13 KiB
Objective-C

//
// ASTextKitCoreTextAdditions.m
// Texture
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
// grant of patent rights can be found in the PATENTS file in the same directory.
//
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASTextKitCoreTextAdditions.h>
#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,
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;
NSString *fontName = (__bridge_transfer NSString *)CTFontCopyPostScriptName(coreTextFont);
CGFloat fontSize = CTFontGetSize(coreTextFont);
UIFont *font = [UIFont fontWithName:fontName size:fontSize];
ASDisplayNodeCAssertNotNil(font, @"unable to load font %@ with size %f", fontName, fontSize);
if (font == nil) {
// Gracefully fail if we were unable to load the font.
font = [UIFont systemFontOfSize:fontSize];
}
cleanAttributes[NSFontAttributeName] = font;
}
// 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] && ![coreTextValue isKindOfClass:[NSParagraphStyle class]]) {
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)
+ (instancetype)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextParagraphStyle;
{
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;
// kCTParagraphStyleSpecifierLineSpacing -> lineSpacing
// Note that kCTParagraphStyleSpecifierLineSpacing is deprecated and will die soon. We should not be using it.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CGFloat lineSpacing;
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierLineSpacing, sizeof(lineSpacing), &lineSpacing))
newParagraphStyle.lineSpacing = lineSpacing;
#pragma clang diagnostic pop
// kCTParagraphStyleSpecifierParagraphSpacing -> paragraphSpacing
CGFloat paragraphSpacing;
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierParagraphSpacing, sizeof(paragraphSpacing), &paragraphSpacing))
newParagraphStyle.paragraphSpacing = paragraphSpacing;
// kCTParagraphStyleSpecifierParagraphSpacingBefore -> paragraphSpacingBefore
CGFloat paragraphSpacingBefore;
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(paragraphSpacingBefore), &paragraphSpacingBefore))
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