Rename CK classes to AS classes

This commit is contained in:
Levi McCallum
2015-11-07 09:02:36 -06:00
committed by Levi McCallum
parent de66819286
commit a0c05ebffc
41 changed files with 252 additions and 1626 deletions

View File

@@ -10,17 +10,17 @@
#import <UIKit/UIKit.h>
#ifndef ComponentKit_CKTextKitAttributes_h
#define ComponentKit_CKTextKitAttributes_h
#ifndef ComponentKit_ASTextKitAttributes_h
#define ComponentKit_ASTextKitAttributes_h
@protocol CKTextKitTruncating;
@protocol ASTextKitTruncating;
extern NSString *const CKTextKitTruncationAttributeName;
extern NSString *const ASTextKitTruncationAttributeName;
/**
Use CKTextKitEntityAttribute as the value of this attribute to embed a link or other interactable content inside the
Use ASTextKitEntityAttribute as the value of this attribute to embed a link or other interactable content inside the
text.
*/
extern NSString *const CKTextKitEntityAttributeName;
extern NSString *const ASTextKitEntityAttributeName;
static inline BOOL _objectsEqual(id<NSObject> obj1, id<NSObject> obj2)
{
@@ -30,19 +30,19 @@ static inline BOOL _objectsEqual(id<NSObject> obj1, id<NSObject> obj2)
/**
All NSObject values in this struct should be copied when passed into the TextComponent.
*/
struct CKTextKitAttributes {
struct ASTextKitAttributes {
/**
The string to be drawn. CKTextKit will not augment this string with default colors, etc. so this must be complete.
The string to be drawn. ASTextKit will not augment this string with default colors, etc. so this must be complete.
*/
NSAttributedString *attributedString;
/**
The string to use as the truncation string, usually just "...". If you have a range of text you would like to
restrict highlighting to (for instance if you have "... Continue Reading", use the CKTextKitTruncationAttributeName
restrict highlighting to (for instance if you have "... Continue Reading", use the ASTextKitTruncationAttributeName
to mark the specific range of the string that should be highlightable.
*/
NSAttributedString *truncationAttributedString;
/**
This is the character set that CKTextKit should attempt to avoid leaving as a trailing character before your
This is the character set that ASTextKit should attempt to avoid leaving as a trailing character before your
truncation token. By default this set includes "\s\t\n\r.,!?:;" so you don't end up with ugly looking truncation
text like "Hey, this is some fancy Truncation!\n\n...". Instead it would be truncated as "Hey, this is some fancy
truncation...". This is not always possible.
@@ -90,7 +90,7 @@ struct CKTextKitAttributes {
We provide an explicit copy function so we can use aggregate initializer syntax while providing copy semantics for
the NSObjects inside.
*/
const CKTextKitAttributes copy() const
const ASTextKitAttributes copy() const
{
return {
[attributedString copy],
@@ -107,7 +107,7 @@ struct CKTextKitAttributes {
};
};
bool operator==(const CKTextKitAttributes &other) const
bool operator==(const ASTextKitAttributes &other) const
{
// These comparisons are in a specific order to reduce the overall cost of this function.
return lineBreakMode == other.lineBreakMode

View File

@@ -8,16 +8,16 @@
*
*/
#import "CKTextKitAttributes.h"
#import "ASTextKitAttributes.h"
#import "CKEqualityHashHelpers.h"
#import "ASEqualityHashHelpers.h"
#include <functional>
NSString *const CKTextKitTruncationAttributeName = @"ck_truncation";
NSString *const CKTextKitEntityAttributeName = @"ck_entity";
NSString *const ASTextKitTruncationAttributeName = @"ck_truncation";
NSString *const ASTextKitEntityAttributeName = @"ck_entity";
size_t CKTextKitAttributes::hash() const
size_t ASTextKitAttributes::hash() const
{
NSUInteger subhashes[] = {
[attributedString hash],

View File

@@ -11,12 +11,12 @@
#import <UIKit/UIKit.h>
/**
A threadsafe container for the TextKit components that CKTextKit uses to lay out and truncate its text.
A threadsafe container for the TextKit components that ASTextKit uses to lay out and truncate its text.
This container is the sole owner and manager of the TextKit classes. This is an important model because of major
thread safety issues inside vanilla TextKit. It provides a central locking location for accessing TextKit methods.
*/
@interface CKTextKitContext : NSObject
@interface ASTextKitContext : NSObject
/**
Initializes a context and its associated TextKit components.

View File

@@ -10,9 +10,9 @@
#import <mutex>
#import "CKTextKitContext.h"
#import "ASTextKitContext.h"
@implementation CKTextKitContext
@implementation ASTextKitContext
{
// All TextKit operations (even non-mutative ones) must be executed serially.
std::mutex _textKitMutex;

View File

@@ -0,0 +1,83 @@
/* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
ASDISPLAYNODE_EXTERN_C_BEGIN
/**
@abstract Returns whether a given attribute is an unsupported Core Text attribute.
@param attributeName The name of the attribute
@discussion The following Core Text attributes are not supported on NSAttributedString, and thus will not be preserved during the conversion:
- kCTForegroundColorFromContextAttributeName
- kCTSuperscriptAttributeName
- kCTGlyphInfoAttributeName
- kCTCharacterShapeAttributeName
- kCTLanguageAttributeName
- kCTRunDelegateAttributeName
- kCTBaselineClassAttributeName
- kCTBaselineInfoAttributeName
- kCTBaselineReferenceInfoAttributeName
- kCTWritingDirectionAttributeName
- kCTUnderlineColorAttributeName
@result Whether attributeName is an unsupported Core Text attribute.
*/
BOOL ASAttributeWithNameIsUnsupportedCoreTextAttribute(NSString *attributeName);
/**
@abstract Returns an attributes dictionary for use by NSAttributedString, given a dictionary of Core Text attributes.
@param coreTextAttributes An NSDictionary whose keys are CFAttributedStringRef attributes.
@discussion The following Core Text attributes are not supported on NSAttributedString, and thus will not be preserved during the conversion:
- kCTForegroundColorFromContextAttributeName
- kCTSuperscriptAttributeName
- kCTGlyphInfoAttributeName
- kCTCharacterShapeAttributeName
- kCTLanguageAttributeName
- kCTRunDelegateAttributeName
- kCTBaselineClassAttributeName
- kCTBaselineInfoAttributeName
- kCTBaselineReferenceInfoAttributeName
- kCTWritingDirectionAttributeName
- kCTUnderlineColorAttributeName
@result An NSDictionary of attributes for use by NSAttributedString.
*/
extern NSDictionary *NSAttributedStringAttributesForCoreTextAttributes(NSDictionary *coreTextAttributes);
/**
@abstract Returns an NSAttributedString whose Core Text attributes have been converted, where possible, to NSAttributedString attributes.
@param dirtyAttributedString An NSAttributedString that may contain Core Text attributes.
@result An NSAttributedString that's preserved as many CFAttributedString attributes as possible.
*/
extern NSAttributedString *ASCleanseAttributedStringOfCoreTextAttributes(NSAttributedString *dirtyAttributedString);
ASDISPLAYNODE_EXTERN_C_END
#pragma mark -
#pragma mark -
@interface NSParagraphStyle (ASTextKitCoreTextAdditions)
/**
@abstract Returns an NSParagraphStyle initialized with the paragraph specifiers from the given CTParagraphStyleRef.
@param coreTextParagraphStyle A Core Text paragraph style.
@discussion It is important to note that not all CTParagraphStyle specifiers are supported by NSParagraphStyle, and consequently, this is a lossy conversion. Notably, the following specifiers will not preserved:
- kCTParagraphStyleSpecifierTabStops
- kCTParagraphStyleSpecifierDefaultTabInterval
- kCTParagraphStyleSpecifierMaximumLineSpacing
- kCTParagraphStyleSpecifierMinimumLineSpacing
- kCTParagraphStyleSpecifierLineSpacingAdjustment
- kCTParagraphStyleSpecifierLineBoundsOptions
@result An NSParagraphStyle initializd with as many of the paragraph specifiers from `coreTextParagraphStyle` as possible.
*/
+ (instancetype)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextParagraphStyle;
@end

View File

@@ -0,0 +1,246 @@
/* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASTextKitCoreTextAdditions.h"
#import <CoreText/CTFont.h>
#import <CoreText/CTStringAttributes.h>
#import "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]) {
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.
CGFloat lineSpacing;
if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierLineSpacing, sizeof(lineSpacing), &lineSpacing))
newParagraphStyle.lineSpacing = lineSpacing;
// 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

View File

@@ -11,7 +11,7 @@
#import <Foundation/Foundation.h>
/**
The object that should be embedded with CKTextKitEntityAttributeName. Please note that the entity you provide MUST
The object that should be embedded with ASTextKitEntityAttributeName. Please note that the entity you provide MUST
implement a proper hash and isEqual function or your application performance will grind to a halt due to
NSMutableAttributedString's usage of a global hash table of all attributes. This means the entity should NOT be a
Foundation Collection (NSArray, NSDictionary, NSSet, etc.) since their hash function is a simple count of the values
@@ -19,7 +19,7 @@
rdar://19352367
*/
@interface CKTextKitEntityAttribute : NSObject
@interface ASTextKitEntityAttribute : NSObject
@property (nonatomic, strong, readonly) id<NSObject> entity;

View File

@@ -8,9 +8,9 @@
*
*/
#import "CKTextKitEntityAttribute.h"
#import "ASTextKitEntityAttribute.h"
@implementation CKTextKitEntityAttribute
@implementation ASTextKitEntityAttribute
- (instancetype)initWithEntity:(id<NSObject>)entity
{
@@ -33,7 +33,7 @@
if (![object isKindOfClass:[self class]]) {
return NO;
}
CKTextKitEntityAttribute *other = (CKTextKitEntityAttribute *)object;
ASTextKitEntityAttribute *other = (ASTextKitEntityAttribute *)object;
return _entity == other.entity || [_entity isEqual:other.entity];
}

View File

@@ -0,0 +1,54 @@
/* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <objc/message.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "ASBaseDefines.h"
ASDISPLAYNODE_INLINE CGFloat ceilPixelValueForScale(CGFloat f, CGFloat scale)
{
// Round up to device pixel (.5 on retina)
return ceilf(f * scale) / scale;
}
ASDISPLAYNODE_INLINE CGSize ceilSizeValue(CGSize s)
{
CGFloat screenScale = [UIScreen mainScreen].scale;
s.width = ceilPixelValueForScale(s.width, screenScale);
s.height = ceilPixelValueForScale(s.height, screenScale);
return s;
}
@interface ASTextKitComponents : NSObject
/**
@abstract Creates the stack of TextKit components.
@param attributedSeedString The attributed string to seed the returned text storage with, or nil to receive an blank text storage.
@param textContainerSize The size of the text-container. Typically, size specifies the constraining width of the layout, and FLT_MAX for height. Pass CGSizeZero if these components will be hooked up to a UITextView, which will manage the text container's size itself.
@return An `ASTextKitComponents` containing the created components. The text view component will be nil.
@discussion The returned components will be hooked up together, so they are ready for use as a system upon return.
*/
+ (ASTextKitComponents *)componentsWithAttributedSeedString:(NSAttributedString *)attributedSeedString
textContainerSize:(CGSize)textContainerSize;
/**
@abstract Returns the bounding size for the text view's text.
@param components The TextKit components to calculate the constrained size of the text for.
@param constrainedWidth The constraining width to be used during text-sizing. Usually, this value should be the receiver's calculated size.
@result A CGSize representing the bounding size for the receiver's text.
*/
- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth;
@property (nonatomic, strong, readonly) NSTextStorage *textStorage;
@property (nonatomic, strong, readonly) NSTextContainer *textContainer;
@property (nonatomic, strong, readonly) NSLayoutManager *layoutManager;
@property (nonatomic, strong) UITextView *textView;
@end

View File

@@ -0,0 +1,57 @@
/* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASTextKitHelpers.h"
@interface ASTextKitComponents ()
// read-write redeclarations
@property (nonatomic, strong, readwrite) NSTextStorage *textStorage;
@property (nonatomic, strong, readwrite) NSTextContainer *textContainer;
@property (nonatomic, strong, readwrite) NSLayoutManager *layoutManager;
@end
@implementation ASTextKitComponents
+ (ASTextKitComponents *)componentsWithAttributedSeedString:(NSAttributedString *)attributedSeedString
textContainerSize:(CGSize)textContainerSize
{
ASTextKitComponents *components = [[ASTextKitComponents alloc] init];
// Create the TextKit component stack with our default configuration.
components.textStorage = (attributedSeedString ? [[NSTextStorage alloc] initWithAttributedString:attributedSeedString] : [[NSTextStorage alloc] init]);
components.layoutManager = [[NSLayoutManager alloc] init];
[components.textStorage addLayoutManager:components.layoutManager];
components.textContainer = [[NSTextContainer alloc] initWithSize:textContainerSize];
components.textContainer.lineFragmentPadding = 0.0; // We want the text laid out up to the very edges of the text-view.
[components.layoutManager addTextContainer:components.textContainer];
return components;
}
- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth
{
ASTextKitComponents *components = self;
// If our text-view's width is already the constrained width, we can use our existing TextKit stack for this sizing calculation.
// Otherwise, we create a temporary stack to size for `constrainedWidth`.
if (CGRectGetWidth(components.textView.bounds) != constrainedWidth) {
components = [ASTextKitComponents componentsWithAttributedSeedString:components.textStorage textContainerSize:CGSizeMake(constrainedWidth, FLT_MAX)];
}
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by -usedRectForTextContainer:).
[components.layoutManager ensureLayoutForTextContainer:components.textContainer];
CGSize textSize = [components.layoutManager usedRectForTextContainer:components.textContainer].size;
return textSize;
}
@end

View File

@@ -8,7 +8,7 @@
*
*/
#import "CKTextKitRenderer.h"
#import "ASTextKitRenderer.h"
typedef void (^ck_text_component_index_block_t)(NSUInteger characterIndex,
CGRect glyphBoundingRect,
@@ -25,13 +25,13 @@ typedef void (^ck_text_component_index_block_t)(NSUInteger characterIndex,
ASTextNodeRendererMeasureOptionBlock uses the cap height option to generate each glyph index, but combines all but the
first and last line rect into a single block. Looks nice for multiline selection.
*/
typedef NS_ENUM(NSUInteger, CKTextKitRendererMeasureOption) {
CKTextKitRendererMeasureOptionLineHeight,
CKTextKitRendererMeasureOptionCapHeight,
CKTextKitRendererMeasureOptionBlock
typedef NS_ENUM(NSUInteger, ASTextKitRendererMeasureOption) {
ASTextKitRendererMeasureOptionLineHeight,
ASTextKitRendererMeasureOptionCapHeight,
ASTextKitRendererMeasureOptionBlock
};
@interface CKTextKitRenderer (Positioning)
@interface ASTextKitRenderer (Positioning)
/**
Returns the bounding rect for the given character range.
@@ -48,7 +48,7 @@ typedef NS_ENUM(NSUInteger, CKTextKitRendererMeasureOption) {
@param textRange The character range for which the rects will be computed. Should be within the range of the
attributedString of this renderer.
@param measureOption The measure option to use for construction of the rects. See CKTextKitRendererMeasureOption
@param measureOption The measure option to use for construction of the rects. See ASTextKitRendererMeasureOption
docs for usage.
@discussion This method is useful for providing highlighting text. Returned rects are in the coordinate space of the
@@ -57,7 +57,7 @@ typedef NS_ENUM(NSUInteger, CKTextKitRendererMeasureOption) {
Triggers initialization of textkit components, truncation, and sizing.
*/
- (NSArray *)rectsForTextRange:(NSRange)textRange
measureOption:(CKTextKitRendererMeasureOption)measureOption;
measureOption:(ASTextKitRendererMeasureOption)measureOption;
/**
Enumerate the text character indexes at a position within the coordinate space of the renderer.

View File

@@ -8,22 +8,22 @@
*
*/
#import "CKTextKitRenderer+Positioning.h"
#import "ASTextKitRenderer+Positioning.h"
#import <CoreText/CoreText.h>
#import "ASAssert.h"
#import "CKTextKitContext.h"
#import "CKTextKitShadower.h"
#import "ASTextKitContext.h"
#import "ASTextKitShadower.h"
static const CGFloat CKTextKitRendererGlyphTouchHitSlop = 5.0;
static const CGFloat CKTextKitRendererTextCapHeightPadding = 1.3;
static const CGFloat ASTextKitRendererGlyphTouchHitSlop = 5.0;
static const CGFloat ASTextKitRendererTextCapHeightPadding = 1.3;
@implementation CKTextKitRenderer (Tracking)
@implementation ASTextKitRenderer (Tracking)
- (NSArray *)rectsForTextRange:(NSRange)textRange
measureOption:(CKTextKitRendererMeasureOption)measureOption
measureOption:(ASTextKitRendererMeasureOption)measureOption
{
__block NSArray *textRects = @[];
[self.context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
@@ -75,7 +75,7 @@ static const CGFloat CKTextKitRendererTextCapHeightPadding = 1.3;
}
if (!CGRectIsNull(lineRect)) {
if (measureOption == CKTextKitRendererMeasureOptionBlock) {
if (measureOption == ASTextKitRendererMeasureOptionBlock) {
// For the block measurement option we store the first & last rect as
// special cases, then merge everything else into a single block rect
if (CGRectIsNull(firstRect)) {
@@ -105,7 +105,7 @@ static const CGFloat CKTextKitRendererTextCapHeightPadding = 1.3;
}
}];
if (measureOption == CKTextKitRendererMeasureOptionBlock) {
if (measureOption == ASTextKitRendererMeasureOptionBlock) {
// Block measure option is handled differently with just 3 vars for the entire range.
if (!CGRectIsNull(firstRect)) {
if (!CGRectIsNull(blockRect)) {
@@ -170,7 +170,7 @@ static const CGFloat CKTextKitRendererTextCapHeightPadding = 1.3;
as an approximation to work around problems in TextKit's glyph sizing.
*/
- (CGRect)_internalRectForGlyphAtIndex:(NSUInteger)glyphIndex
measureOption:(CKTextKitRendererMeasureOption)measureOption
measureOption:(ASTextKitRendererMeasureOption)measureOption
layoutManager:(NSLayoutManager *)layoutManager
textContainer:(NSTextContainer *)textContainer
textStorage:(NSTextStorage *)textStorage
@@ -226,8 +226,8 @@ static const CGFloat CKTextKitRendererTextCapHeightPadding = 1.3;
CGPoint glyphCenter = CGPointMake(CGRectGetMidX(glyphRect), CGRectGetMidY(glyphRect));
CGRect properGlyphRect;
if (measureOption == CKTextKitRendererMeasureOptionCapHeight
|| measureOption == CKTextKitRendererMeasureOptionBlock) {
if (measureOption == ASTextKitRendererMeasureOptionCapHeight
|| measureOption == ASTextKitRendererMeasureOptionBlock) {
CGFloat ascent = CTFontGetAscent(font);
CGFloat descent = CTFontGetDescent(font);
CGFloat capHeight = CTFontGetCapHeight(font);
@@ -237,7 +237,7 @@ static const CGFloat CKTextKitRendererTextCapHeightPadding = 1.3;
// For visual balance, we add the cap height padding above the cap, and
// below the baseline, we scale by the descent so it grows with the size of
// the text.
CGFloat topPadding = CKTextKitRendererTextCapHeightPadding * descent;
CGFloat topPadding = ASTextKitRendererTextCapHeightPadding * descent;
CGFloat bottomPadding = topPadding;
properGlyphRect = CGRectMake(glyphCenter.x - advance * 0.5,
@@ -263,7 +263,7 @@ static const CGFloat CKTextKitRendererTextCapHeightPadding = 1.3;
// This method is a little complex because it has to call out to client code from inside an enumeration that needs
// to achieve a lock on the textkit components. It cannot call out to client code from within that lock so we just
// perform the textkit-locked ops inside the locked context.
CKTextKitContext *lockingContext = self.context;
ASTextKitContext *lockingContext = self.context;
CGPoint internalPosition = [self.shadower offsetPointWithExternalPoint:externalPosition];
__block BOOL invalidPosition = NO;
[lockingContext performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
@@ -314,7 +314,7 @@ static const CGFloat CKTextKitRendererTextCapHeightPadding = 1.3;
characterIndex = [layoutManager characterIndexForGlyphAtIndex:glyphIndex];
glyphRect = [self _internalRectForGlyphAtIndex:glyphIndex
measureOption:CKTextKitRendererMeasureOptionLineHeight
measureOption:ASTextKitRendererMeasureOptionLineHeight
layoutManager:layoutManager
textContainer:textContainer
textStorage:textStorage];
@@ -322,7 +322,7 @@ static const CGFloat CKTextKitRendererTextCapHeightPadding = 1.3;
// Sometimes TextKit plays jokes on us and returns glyphs that really aren't close to the point in question.
// Silly TextKit...
if (!isValidGlyph || !CGRectContainsPoint(CGRectInset(glyphRect, -CKTextKitRendererGlyphTouchHitSlop, -CKTextKitRendererGlyphTouchHitSlop), currentPoint)) {
if (!isValidGlyph || !CGRectContainsPoint(CGRectInset(glyphRect, -ASTextKitRendererGlyphTouchHitSlop, -ASTextKitRendererGlyphTouchHitSlop), currentPoint)) {
continue;
}
@@ -343,7 +343,7 @@ static const CGFloat CKTextKitRendererTextCapHeightPadding = 1.3;
}
// Take everything after our final character as trailing space.
NSArray *finalRects = [self rectsForTextRange:NSMakeRange([textStorage length] - 1, 1) measureOption:CKTextKitRendererMeasureOptionLineHeight];
NSArray *finalRects = [self rectsForTextRange:NSMakeRange([textStorage length] - 1, 1) measureOption:ASTextKitRendererMeasureOptionLineHeight];
CGRect finalGlyphRect = [[finalRects lastObject] CGRectValue];
CGPoint origin = CGPointMake(CGRectGetMaxX(finalGlyphRect), CGRectGetMinY(finalGlyphRect));
CGSize size = CGSizeMake(calculatedSize.width - origin.x, calculatedSize.height - origin.y);

View File

@@ -8,21 +8,21 @@
*
*/
#import "CKTextKitRenderer.h"
#import "ASTextKitRenderer.h"
/**
Application extensions to NSTextCheckingType. We're allowed to do this (see NSTextCheckingAllCustomTypes).
*/
static uint64_t const CKTextKitTextCheckingTypeEntity = 1ULL << 33;
static uint64_t const CKTextKitTextCheckingTypeTruncation = 1ULL << 34;
static uint64_t const ASTextKitTextCheckingTypeEntity = 1ULL << 33;
static uint64_t const ASTextKitTextCheckingTypeTruncation = 1ULL << 34;
@class CKTextKitEntityAttribute;
@class ASTextKitEntityAttribute;
@interface CKTextKitTextCheckingResult : NSTextCheckingResult
@property (nonatomic, strong, readonly) CKTextKitEntityAttribute *entityAttribute;
@interface ASTextKitTextCheckingResult : NSTextCheckingResult
@property (nonatomic, strong, readonly) ASTextKitEntityAttribute *entityAttribute;
@end
@interface CKTextKitRenderer (TextChecking)
@interface ASTextKitRenderer (TextChecking)
- (NSTextCheckingResult *)textCheckingResultAtPoint:(CGPoint)point;

View File

@@ -8,14 +8,14 @@
*
*/
#import "CKTextKitRenderer+TextChecking.h"
#import "ASTextKitRenderer+TextChecking.h"
#import "CKTextKitAttributes.h"
#import "CKTextKitEntityAttribute.h"
#import "CKTextKitRenderer+Positioning.h"
#import "CKTextKitTailTruncater.h"
#import "ASTextKitAttributes.h"
#import "ASTextKitEntityAttribute.h"
#import "ASTextKitRenderer+Positioning.h"
#import "ASTextKitTailTruncater.h"
@implementation CKTextKitTextCheckingResult
@implementation ASTextKitTextCheckingResult
{
// Be explicit about the fact that we are overriding the super class' implementation of -range and -resultType
@@ -27,7 +27,7 @@
}
- (instancetype)initWithType:(NSTextCheckingType)type
entityAttribute:(CKTextKitEntityAttribute *)entityAttribute
entityAttribute:(ASTextKitEntityAttribute *)entityAttribute
range:(NSRange)range
{
if ((self = [super init])) {
@@ -50,7 +50,7 @@
@end
@implementation CKTextKitRenderer (TextChecking)
@implementation ASTextKitRenderer (TextChecking)
- (NSTextCheckingResult *)textCheckingResultAtPoint:(CGPoint)point
{
@@ -62,7 +62,7 @@
NSRange visibleRange = self.truncater.visibleRanges[0];
__block NSRange truncationTokenRange = { NSNotFound, 0 };
[truncationAttributedString enumerateAttribute:CKTextKitTruncationAttributeName inRange:NSMakeRange(0, truncationAttributedString.length)
[truncationAttributedString enumerateAttribute:ASTextKitTruncationAttributeName inRange:NSMakeRange(0, truncationAttributedString.length)
options:0
usingBlock:^(id value, NSRange range, BOOL *stop) {
if (value != nil && range.length > 0) {
@@ -79,15 +79,15 @@
[self enumerateTextIndexesAtPosition:point usingBlock:^(NSUInteger index, CGRect glyphBoundingRect, BOOL *stop){
if (index >= truncationTokenRange.location) {
result = [[CKTextKitTextCheckingResult alloc] initWithType:CKTextKitTextCheckingTypeTruncation
result = [[ASTextKitTextCheckingResult alloc] initWithType:ASTextKitTextCheckingTypeTruncation
entityAttribute:nil
range:truncationTokenRange];
} else {
NSRange range;
NSDictionary *attributes = [attributedString attributesAtIndex:index effectiveRange:&range];
CKTextKitEntityAttribute *entityAttribute = attributes[CKTextKitEntityAttributeName];
ASTextKitEntityAttribute *entityAttribute = attributes[ASTextKitEntityAttributeName];
if (entityAttribute) {
result = [[CKTextKitTextCheckingResult alloc] initWithType:CKTextKitTextCheckingTypeEntity
result = [[ASTextKitTextCheckingResult alloc] initWithType:ASTextKitTextCheckingTypeEntity
entityAttribute:entityAttribute
range:range];
}

View File

@@ -12,14 +12,14 @@
#import <UIKit/UIKit.h>
#import "CKTextKitAttributes.h"
#import "ASTextKitAttributes.h"
@class CKTextKitContext;
@class CKTextKitShadower;
@protocol CKTextKitTruncating;
@class ASTextKitContext;
@class ASTextKitShadower;
@protocol ASTextKitTruncating;
/**
CKTextKitRenderer is a modular object that is responsible for laying out and drawing text.
ASTextKitRenderer is a modular object that is responsible for laying out and drawing text.
A renderer will hold onto the TextKit layouts for the given attributes after initialization. This may constitute a
large amount of memory for large enough applications, so care must be taken when keeping many of these around in-memory
@@ -33,23 +33,23 @@
coordinate space. Padding will be added for you in order to ensure clipping does not occur, and additional information
on this transform is available via the shadower should you need it.
*/
@interface CKTextKitRenderer : NSObject
@interface ASTextKitRenderer : NSObject
/**
Designated Initializer
dvlkferufedgjnhjjfhldjedlunvtdtv
@discussion Sizing will occur as a result of initialization, so be careful when/where you use this.
*/
- (instancetype)initWithTextKitAttributes:(const CKTextKitAttributes &)textComponentAttributes
- (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)textComponentAttributes
constrainedSize:(const CGSize)constrainedSize;
@property (nonatomic, strong, readonly) CKTextKitContext *context;
@property (nonatomic, strong, readonly) ASTextKitContext *context;
@property (nonatomic, strong, readonly) id<CKTextKitTruncating> truncater;
@property (nonatomic, strong, readonly) id<ASTextKitTruncating> truncater;
@property (nonatomic, strong, readonly) CKTextKitShadower *shadower;
@property (nonatomic, strong, readonly) ASTextKitShadower *shadower;
@property (nonatomic, assign, readonly) CKTextKitAttributes attributes;
@property (nonatomic, assign, readonly) ASTextKitAttributes attributes;
@property (nonatomic, assign, readonly) CGSize constrainedSize;

View File

@@ -8,14 +8,14 @@
*
*/
#import "CKTextKitRenderer.h"
#import "ASTextKitRenderer.h"
#import "ASAssert.h"
#import "CKTextKitContext.h"
#import "CKTextKitShadower.h"
#import "CKTextKitTailTruncater.h"
#import "CKTextKitTruncating.h"
#import "ASTextKitContext.h"
#import "ASTextKitShadower.h"
#import "ASTextKitTailTruncater.h"
#import "ASTextKitTruncating.h"
static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
{
@@ -30,20 +30,20 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
return truncationCharacterSet;
}
@implementation CKTextKitRenderer {
@implementation ASTextKitRenderer {
CGSize _calculatedSize;
}
#pragma mark - Initialization
- (instancetype)initWithTextKitAttributes:(const CKTextKitAttributes &)attributes
- (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)attributes
constrainedSize:(const CGSize)constrainedSize
{
if (self = [super init]) {
_constrainedSize = constrainedSize;
_attributes = attributes;
_shadower = [[CKTextKitShadower alloc] initWithShadowOffset:attributes.shadowOffset
_shadower = [[ASTextKitShadower alloc] initWithShadowOffset:attributes.shadowOffset
shadowColor:attributes.shadowColor
shadowOpacity:attributes.shadowOpacity
shadowRadius:attributes.shadowRadius];
@@ -51,14 +51,14 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
// We must inset the constrained size by the size of the shadower.
CGSize shadowConstrainedSize = [_shadower insetSizeWithConstrainedSize:_constrainedSize];
_context = [[CKTextKitContext alloc] initWithAttributedString:attributes.attributedString
_context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString
lineBreakMode:attributes.lineBreakMode
maximumNumberOfLines:attributes.maximumNumberOfLines
exclusionPaths:attributes.exclusionPaths
constrainedSize:shadowConstrainedSize
layoutManagerFactory:attributes.layoutManagerFactory];
_truncater = [[CKTextKitTailTruncater alloc] initWithContext:_context
_truncater = [[ASTextKitTailTruncater alloc] initWithContext:_context
truncationAttributedString:attributes.truncationAttributedString
avoidTailTruncationSet:attributes.avoidTailTruncationSet ?: _defaultAvoidTruncationCharacterSet()
constrainedSize:shadowConstrainedSize];

View File

@@ -13,7 +13,7 @@
/**
* @abstract an immutable class for calculating shadow padding drawing a shadowed background for text
*/
@interface CKTextKitShadower : NSObject
@interface ASTextKitShadower : NSObject
- (instancetype)initWithShadowOffset:(CGSize)shadowOffset
shadowColor:(UIColor *)shadowColor

View File

@@ -8,7 +8,7 @@
*
*/
#import "CKTextKitShadower.h"
#import "ASTextKitShadower.h"
static inline CGSize _insetSize(CGSize size, UIEdgeInsets insets)
{
@@ -25,7 +25,7 @@ static inline UIEdgeInsets _invertInsets(UIEdgeInsets insets)
};
}
@implementation CKTextKitShadower {
@implementation ASTextKitShadower {
UIEdgeInsets _calculatedShadowPadding;
}

View File

@@ -10,8 +10,8 @@
#import <UIKit/UIKit.h>
#import "CKTextKitTruncating.h"
#import "ASTextKitTruncating.h"
@interface CKTextKitTailTruncater : NSObject <CKTextKitTruncating>
@interface ASTextKitTailTruncater : NSObject <ASTextKitTruncating>
@end

View File

@@ -10,12 +10,12 @@
#import "ASAssert.h"
#import "CKTextKitContext.h"
#import "CKTextKitTailTruncater.h"
#import "ASTextKitContext.h"
#import "ASTextKitTailTruncater.h"
@implementation CKTextKitTailTruncater
@implementation ASTextKitTailTruncater
{
__weak CKTextKitContext *_context;
__weak ASTextKitContext *_context;
NSAttributedString *_truncationAttributedString;
NSCharacterSet *_avoidTailTruncationSet;
CGSize _constrainedSize;
@@ -23,7 +23,7 @@
@synthesize visibleRanges = _visibleRanges;
@synthesize truncationStringRect = _truncationStringRect;
- (instancetype)initWithContext:(CKTextKitContext *)context
- (instancetype)initWithContext:(ASTextKitContext *)context
truncationAttributedString:(NSAttributedString *)truncationAttributedString
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
constrainedSize:(CGSize)constrainedSize
@@ -69,7 +69,7 @@
BOOL leftAligned = CGRectGetMinX(lastLineRect) == CGRectGetMinX(lastLineUsedRect) || !rtlWritingDirection;
// Calculate the bounding rectangle for the truncation message
CKTextKitContext *truncationContext = [[CKTextKitContext alloc] initWithAttributedString:_truncationAttributedString
ASTextKitContext *truncationContext = [[ASTextKitContext alloc] initWithAttributedString:_truncationAttributedString
lineBreakMode:NSLineBreakByWordWrapping
maximumNumberOfLines:1
exclusionPaths:nil

View File

@@ -12,9 +12,9 @@
#import <UIKit/UIKit.h>
#import "CKTextKitRenderer.h"
#import "ASTextKitRenderer.h"
@protocol CKTextKitTruncating <NSObject>
@protocol ASTextKitTruncating <NSObject>
@property (nonatomic, assign, readonly) std::vector<NSRange> visibleRanges;
@property (nonatomic, assign, readonly) CGRect truncationStringRect;
@@ -29,7 +29,7 @@
The truncater should not store a strong reference to the context to prevent retain cycles.
*/
- (instancetype)initWithContext:(CKTextKitContext *)context
- (instancetype)initWithContext:(ASTextKitContext *)context
truncationAttributedString:(NSAttributedString *)truncationAttributedString
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
constrainedSize:(CGSize)constrainedSize;

View File

@@ -0,0 +1,12 @@
/* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#pragma once
// Use this attribute name to add "word kerning"
static NSString *const ASTextNodeWordKerningAttributeName = @"ASAttributedStringWordKerning";

View File

@@ -0,0 +1,30 @@
/* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import <UIKit/NSLayoutManager.h>
/**
@abstract This class acts as the NSLayoutManagerDelegate for ASTextNode.
@discussion Its current job is word kerning, i.e. adjusting the width of spaces to match the set
wordKernedSpaceWidth. If word kerning is not needed, set the layoutManager's delegate to nil.
*/
@interface ASTextNodeWordKerner : NSObject <NSLayoutManagerDelegate>
/**
The following @optional NSLayoutManagerDelegate methods are implemented:
- (NSUInteger)layoutManager:(NSLayoutManager *)layoutManager shouldGenerateGlyphs:(const CGGlyph *)glyphs properties:(const NSGlyphProperty *)props characterIndexes:(const NSUInteger *)charIndexes font:(UIFont *)aFont forGlyphRange:(NSRange)glyphRange NS_AVAILABLE_IOS(7_0);
- (NSControlCharacterAction)layoutManager:(NSLayoutManager *)layoutManager shouldUseAction:(NSControlCharacterAction)action forControlCharacterAtIndex:(NSUInteger)charIndex NS_AVAILABLE_IOS(7_0);
- (CGRect)layoutManager:(NSLayoutManager *)layoutManager boundingBoxForControlGlyphAtIndex:(NSUInteger)glyphIndex forTextContainer:(NSTextContainer *)textContainer proposedLineFragment:(CGRect)proposedRect glyphPosition:(CGPoint)glyphPosition characterIndex:(NSUInteger)charIndex NS_AVAILABLE_IOS(7_0);
*/
@end

View File

@@ -0,0 +1,129 @@
/* 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 root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASTextNodeWordKerner.h"
#import <UIKit/UIKit.h>
#import "ASTextNodeTypes.h"
@implementation ASTextNodeWordKerner
#pragma mark - NSLayoutManager Delegate
- (NSUInteger)layoutManager:(NSLayoutManager *)layoutManager shouldGenerateGlyphs:(const CGGlyph *)glyphs properties:(const NSGlyphProperty *)properties characterIndexes:(const NSUInteger *)characterIndexes font:(UIFont *)aFont forGlyphRange:(NSRange)glyphRange
{
NSUInteger glyphCount = glyphRange.length;
NSGlyphProperty *newGlyphProperties = NULL;
BOOL usesWordKerning = NO;
// If our typing attributes specify word kerning, specify the spaces as whitespace control characters so we can customize their width.
// Are any of the characters spaces?
NSString *textStorageString = layoutManager.textStorage.string;
for (NSUInteger arrayIndex = 0; arrayIndex < glyphCount; arrayIndex++) {
NSUInteger characterIndex = characterIndexes[arrayIndex];
if ([textStorageString characterAtIndex:characterIndex] != ' ')
continue;
// If we've set the whitespace control character for this space already, we have nothing to do.
if (properties[arrayIndex] == NSGlyphPropertyControlCharacter) {
usesWordKerning = YES;
continue;
}
// Create new glyph properties, if necessary.
if (!newGlyphProperties) {
newGlyphProperties = (NSGlyphProperty *)malloc(sizeof(NSGlyphProperty) * glyphCount);
memcpy(newGlyphProperties, properties, (sizeof(NSGlyphProperty) * glyphCount));
}
// It's a space. Make it a whitespace control character.
newGlyphProperties[arrayIndex] = NSGlyphPropertyControlCharacter;
}
// If we don't have any custom glyph properties, return 0 to indicate to the layout manager that it should use the standard glyphs+properties.
if (!newGlyphProperties) {
if (usesWordKerning) {
// If the text does use word kerning we have to make sure we return the correct glyphCount, or the layout manager will just use the default properties and ignore our kerning.
[layoutManager setGlyphs:glyphs properties:properties characterIndexes:characterIndexes font:aFont forGlyphRange:glyphRange];
return glyphCount;
} else {
return 0;
}
}
// Otherwise, use our custom glyph properties.
[layoutManager setGlyphs:glyphs properties:newGlyphProperties characterIndexes:characterIndexes font:aFont forGlyphRange:glyphRange];
free(newGlyphProperties);
return glyphCount;
}
- (NSControlCharacterAction)layoutManager:(NSLayoutManager *)layoutManager shouldUseAction:(NSControlCharacterAction)defaultAction forControlCharacterAtIndex:(NSUInteger)characterIndex
{
// If it's a space character and we have custom word kerning, use the whitespace action control character.
if ([layoutManager.textStorage.string characterAtIndex:characterIndex] == ' ')
return NSControlCharacterWhitespaceAction;
return defaultAction;
}
- (CGRect)layoutManager:(NSLayoutManager *)layoutManager boundingBoxForControlGlyphAtIndex:(NSUInteger)glyphIndex forTextContainer:(NSTextContainer *)textContainer proposedLineFragment:(CGRect)proposedRect glyphPosition:(CGPoint)glyphPosition characterIndex:(NSUInteger)characterIndex
{
CGFloat wordKernedSpaceWidth = [self _wordKernedSpaceWidthForCharacterAtIndex:characterIndex atGlyphPosition:glyphPosition forTextContainer:textContainer layoutManager:layoutManager];
return CGRectMake(glyphPosition.x, glyphPosition.y, wordKernedSpaceWidth, CGRectGetHeight(proposedRect));
}
- (CGFloat)_wordKernedSpaceWidthForCharacterAtIndex:(NSUInteger)characterIndex atGlyphPosition:(CGPoint)glyphPosition forTextContainer:(NSTextContainer *)textContainer layoutManager:(NSLayoutManager *)layoutManager
{
// We use a map table for pointer equality and non-copying keys.
static NSMapTable *spaceSizes;
// NSMapTable is a defined thread unsafe class, so we need to synchronize
// access in a light manner. So we use dispatch_sync on this queue for all
// access to the map table.
static dispatch_queue_t mapQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
spaceSizes = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory capacity:1];
mapQueue = dispatch_queue_create("org.AsyncDisplayKit.wordKerningQueue", DISPATCH_QUEUE_SERIAL);
});
CGFloat ordinarySpaceWidth;
UIFont *font = [layoutManager.textStorage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL];
CGFloat wordKerning = [[layoutManager.textStorage attribute:ASTextNodeWordKerningAttributeName atIndex:characterIndex effectiveRange:NULL] floatValue];
__block NSNumber *ordinarySpaceSizeValue;
dispatch_sync(mapQueue, ^{
ordinarySpaceSizeValue = [spaceSizes objectForKey:font];
});
if (ordinarySpaceSizeValue == nil) {
ordinarySpaceWidth = [@" " sizeWithAttributes:@{ NSFontAttributeName : font }].width;
dispatch_async(mapQueue, ^{
[spaceSizes setObject:@(ordinarySpaceWidth) forKey:font];
});
} else {
ordinarySpaceWidth = [ordinarySpaceSizeValue floatValue];
}
CGFloat totalKernedWidth = (ordinarySpaceWidth + wordKerning);
// TextKit normally handles whitespace by increasing the advance of the previous glyph, rather than displaying an
// actual glyph for the whitespace itself. However, in order to implement word kerning, we explicitly require a
// discrete glyph whose bounding box we can specify. The problem is that TextKit does not know this glyph is
// invisible. From TextKit's perspective, this whitespace glyph is a glyph that MUST be displayed. Thus when it
// comes to determining linebreaks, the width of this trailing whitespace glyph is considered. This causes
// our text to wrap sooner than it otherwise would, as room is allocated at the end of each line for a glyph that
// isn't actually visible. To implement our desired behavior, we check to see if the current whitespace glyph
// would break to the next line. If it breaks to the next line, then this constitutes trailing whitespace, and
// we specify enough room to fill up the remainder of the line, but nothing more.
if (glyphPosition.x + totalKernedWidth > textContainer.size.width) {
return (textContainer.size.width - glyphPosition.x);
}
return totalKernedWidth;
}
@end