ComponentTextKit/CKTextComponentLayer.mm (101 lines of code) (raw):
/*
* 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 "CKTextComponentLayer.h"
#import <ComponentKit/CKInternalHelpers.h>
#import <ComponentTextKit/CKTextKitAttributes.h>
#import <ComponentTextKit/CKTextKitRenderer.h>
#import <ComponentTextKit/CKTextKitRendererCache.h>
#import <RenderCore/RCAssert.h>
#import "CKTextComponentLayerHighlighter.h"
static CK::TextKit::Renderer::Cache *rasterContentsCache()
{
// 6MB raster contents cache that evicts 20% of the least recently used bitmaps it contains when it hits 6MB
static CK::TextKit::Renderer::Cache *__rasterContentsCache (new CK::TextKit::Renderer::Cache("CKTextComponentRasterContentsCache", 6 * 1024 * 1025, 0.2));
return __rasterContentsCache;
}
@implementation CKTextComponentLayer
{
CKTextComponentLayerHighlighter *_highlighter;
}
+ (id)defaultValueForKey:(NSString *)key
{
if ([key isEqualToString:@"contentsScale"]) {
return @(CKScreenScale());
} else if ([key isEqualToString:@"backgroundColor"]) {
return (id)[UIColor whiteColor].CGColor;
} else if ([key isEqualToString:@"opaque"]) {
return (id)kCFBooleanTrue;
} else if ([key isEqualToString:@"userInteractionEnabled"]) {
return (id)kCFBooleanTrue;
} else if ([key isEqualToString:@"needsDisplayOnBoundsChange"]) {
return (id)kCFBooleanTrue;
}
return [super defaultValueForKey:key];
}
- (void)setNeedsDisplayOnBoundsChange:(BOOL)needsDisplayOnBoundsChange
{
// Don't allow this property to be disabled. Unfortunately, UIView will turn this off when setting the
// backgroundColor, for reasons that cannot be understood. Even worse, it doesn't ever set it back, so it will
// subsequently stay off. Just make sure that it never gets overridden, because the text will not be drawn in the
// correct way (or even at all) if this is set to NO.
if (needsDisplayOnBoundsChange) {
[super setNeedsDisplayOnBoundsChange:needsDisplayOnBoundsChange];
}
}
- (void)setRenderer:(CKTextKitRenderer *)renderer
{
RCAssertMainThread();
if (renderer != _renderer) {
if (renderer && _renderer) {
if (renderer.attributes == _renderer.attributes
&& CGSizeEqualToSize(renderer.constrainedSize, renderer.constrainedSize)) {
// If the renderers are identical there's no point in re-rendering
_renderer = renderer;
return;
} else {
// If the renderers are truly not equal we need to nil out the contents so we don't display old text
// from a previous renderer.
self.contents = nil;
}
}
_renderer = renderer;
[self setNeedsDisplay];
}
}
- (NSObject *)drawParameters
{
return _renderer;
}
- (id)willDisplayAsynchronouslyWithDrawParameters:(id<NSObject>)drawParameters
{
return rasterContentsCache()->objectForKey({_renderer.attributes, _renderer.constrainedSize});
}
- (void)didDisplayAsynchronously:(id)newContents withDrawParameters:(id<NSObject>)drawParameters
{
if (newContents) {
CGImageRef imageRef = (__bridge CGImageRef)newContents;
NSUInteger bytes = CGImageGetBytesPerRow(imageRef) * CGImageGetHeight(imageRef);
rasterContentsCache()->cacheObject({_renderer.attributes, _renderer.constrainedSize}, newContents, bytes);
}
}
+ (void)drawInContext:(CGContextRef)context parameters:(CKTextKitRenderer *)renderer
{
CGRect boundsRect = CGContextGetClipBoundingBox(context);
[renderer drawInContext:context bounds:boundsRect];
}
- (void)drawInContext:(CGContextRef)ctx
{
// When we're drawing synchronously we need to manually fill the bg color because CKAsyncLayer doesn't.
if (self.opaque && self.backgroundColor != NULL) {
CGRect boundsRect = CGContextGetClipBoundingBox(ctx);
CGContextSetFillColorWithColor(ctx, self.backgroundColor);
CGContextFillRect(ctx, boundsRect);
}
[super drawInContext:ctx];
}
#pragma mark - Highlighting
- (CKTextComponentLayerHighlighter *)highlighter
{
RCAssertMainThread();
if (!_highlighter) {
_highlighter = [[CKTextComponentLayerHighlighter alloc] initWithTextComponentLayer:self];
}
return _highlighter;
}
- (void)layoutSublayers
{
// Do not generate a highlighter if one doesn't already exist
if (_highlighter) {
[_highlighter layoutHighlight];
}
[super layoutSublayers];
}
@end