ComponentTextKit/CKTextComponent.mm (86 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 "CKTextComponent.h" #import <memory> #import <vector> #import <ComponentKit/CKComponentInternal.h> #import <ComponentTextKit/CKTextKitRenderer.h> #import <ComponentTextKit/CKTextKitRendererCache.h> #import <ComponentKit/CKInternalHelpers.h> #import "CKTextComponentView.h" @protocol CKSystraceListener; static CK::TextKit::Renderer::Cache *sharedRendererCache() { // This cache is sized arbitrarily static CK::TextKit::Renderer::Cache *__rendererCache (new CK::TextKit::Renderer::Cache("CKTextComponentRendererCache", 500, 0.2)); return __rendererCache; } /** The concept here is that neither the component nor layout should ever have a strong reference to the renderer object. This is to reduce memory load when loading thousands and thousands of text components into memory at once. Instead we maintain a LRU renderer cache that is queried via stack-allocated keys. */ static CKTextKitRenderer *rendererForAttributes(CKTextKitAttributes &attributes, CGSize constrainedSize) { CK::TextKit::Renderer::Cache *cache = sharedRendererCache(); const CK::TextKit::Renderer::Key key { attributes, constrainedSize }; CKTextKitRenderer *renderer = cache->objectForKey(key); if (!renderer) { renderer = [[CKTextKitRenderer alloc] initWithTextKitAttributes:attributes constrainedSize:constrainedSize]; cache->cacheObject(key, renderer, 1); } return renderer; } @implementation CKTextComponent { CKTextKitAttributes _attributes; CKTextComponentAccessibilityContext _accessibilityContext; } + (instancetype)newWithTextAttributes:(const CKTextKitAttributes &)attributes viewAttributes:(const CKViewComponentAttributeValueMap &)viewAttributes options:(const CKTextComponentOptions &)options size:(const RCComponentSize &)size { CKTextKitAttributes copyAttributes = attributes.copy(); CKViewComponentAttributeValueMap copiedMap = viewAttributes; copiedMap.insert({CKComponentViewAttribute::LayerAttribute(@selector(setDisplayMode:)), @(options.displayMode)}); CKTextComponent *c = [super newWithView:{ [CKTextComponentView class], std::move(copiedMap), { .isAccessibilityElement = options.accessibilityContext.isAccessibilityElement, .accessibilityLabel = options.accessibilityContext.accessibilityLabel.hasText() ? options.accessibilityContext.accessibilityLabel : ^{ return copyAttributes.attributedString.string; } } } size:size]; if (c) { c->_attributes = copyAttributes; c->_accessibilityContext = options.accessibilityContext; } return c; } - (RCLayout)computeLayoutThatFits:(CKSizeRange)constrainedSize { const CKTextKitRenderer *renderer = rendererForAttributes(_attributes, constrainedSize.max); return { self, constrainedSize.clamp({ CKCeilPixelValue(renderer.size.width), CKCeilPixelValue(renderer.size.height) }), {} }; } - (CK::Component::MountResult)mountInContext:(const CK::Component::MountContext &)context layout:(const RCLayout &)layout supercomponent:(CKComponent *)supercomponent { CK::Component::MountResult result = [super mountInContext:context layout:layout supercomponent:supercomponent]; CKTextComponentView *view = (CKTextComponentView *)result.contextForChildren.viewManager->view; CKTextKitRenderer *renderer = rendererForAttributes(_attributes, layout.size); view.renderer = renderer; view.isAccessibilityElement = _accessibilityContext.isAccessibilityElement.boolValue; view.accessibilityLabel = _accessibilityContext.accessibilityLabel.hasText() ? _accessibilityContext.accessibilityLabel.value() : _attributes.attributedString.string; return result; } @end