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