diff --git a/Schemas/configuration.json b/Schemas/configuration.json index 64a4c790b..3b90c1ed8 100644 --- a/Schemas/configuration.json +++ b/Schemas/configuration.json @@ -26,7 +26,8 @@ "exp_skip_a11y_wait", "exp_new_default_cell_layout_mode", "exp_dispatch_apply", - "exp_image_downloader_priority" + "exp_image_downloader_priority", + "exp_text_drawing" ] } } diff --git a/Source/ASExperimentalFeatures.h b/Source/ASExperimentalFeatures.h index f3e6b295b..b15845b4e 100644 --- a/Source/ASExperimentalFeatures.h +++ b/Source/ASExperimentalFeatures.h @@ -32,6 +32,7 @@ typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) { ASExperimentalNewDefaultCellLayoutMode = 1 << 11, // exp_new_default_cell_layout_mode ASExperimentalDispatchApply = 1 << 12, // exp_dispatch_apply ASExperimentalImageDownloaderPriority = 1 << 13, // exp_image_downloader_priority + ASExperimentalTextDrawing = 1 << 14, // exp_text_drawing ASExperimentalFeatureAll = 0xFFFFFFFF }; diff --git a/Source/ASExperimentalFeatures.mm b/Source/ASExperimentalFeatures.mm index d1f9a7966..89a876c57 100644 --- a/Source/ASExperimentalFeatures.mm +++ b/Source/ASExperimentalFeatures.mm @@ -25,8 +25,8 @@ @"exp_skip_a11y_wait", @"exp_new_default_cell_layout_mode", @"exp_dispatch_apply", - @"exp_image_downloader_priority"])); - + @"exp_image_downloader_priority", + @"exp_text_drawing"])); if (flags == ASExperimentalFeatureAll) { return allNames; } diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index 7ce9c8322..e872a0929 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -18,6 +18,7 @@ #import #import +#import #import #import #import @@ -140,6 +141,9 @@ @interface ASTextNodeDrawParameter : NSObject { ASTextKitAttributes _rendererAttributes; UIColor *_backgroundColor; UIEdgeInsets _textContainerInsets; + CGFloat _contentScale; + BOOL _opaque; + CGRect _bounds; } @end @@ -148,12 +152,18 @@ @implementation ASTextNodeDrawParameter - (instancetype)initWithRendererAttributes:(ASTextKitAttributes)rendererAttributes backgroundColor:(/*nullable*/ UIColor *)backgroundColor textContainerInsets:(UIEdgeInsets)textContainerInsets + contentScale:(CGFloat)contentScale + opaque:(BOOL)opaque + bounds:(CGRect)bounds { self = [super init]; if (self != nil) { _rendererAttributes = rendererAttributes; _backgroundColor = backgroundColor; _textContainerInsets = textContainerInsets; + _contentScale = contentScale; + _opaque = opaque; + _bounds = bounds; } return self; } @@ -526,31 +536,74 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer return [[ASTextNodeDrawParameter alloc] initWithRendererAttributes:[self _locked_rendererAttributes] backgroundColor:self.backgroundColor - textContainerInsets:_textContainerInset]; + textContainerInsets:_textContainerInset + contentScale:_contentsScaleForDisplay + opaque:self.isOpaque + bounds:[self threadSafeBounds]]; } -+ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing ++ (UIImage *)displayWithParameters:(id)parameters isCancelled:(NS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelled { ASTextNodeDrawParameter *drawParameter = (ASTextNodeDrawParameter *)parameters; - UIColor *backgroundColor = (isRasterizing || drawParameter == nil) ? nil : drawParameter->_backgroundColor; - UIEdgeInsets textContainerInsets = drawParameter ? drawParameter->_textContainerInsets : UIEdgeInsetsZero; - ASTextKitRenderer *renderer = [drawParameter rendererForBounds:bounds]; - CGContextRef context = UIGraphicsGetCurrentContext(); - ASDisplayNodeAssert(context, @"This is no good without a context."); - - CGContextSaveGState(context); - CGContextTranslateCTM(context, textContainerInsets.left, textContainerInsets.top); + if (drawParameter->_bounds.size.width <= 0 || drawParameter->_bounds.size.height <= 0) { + return nil; + } + + UIImage *result = nil; + UIColor *backgroundColor = drawParameter->_backgroundColor; + UIEdgeInsets textContainerInsets = drawParameter ? drawParameter->_textContainerInsets : UIEdgeInsetsZero; + ASTextKitRenderer *renderer = [drawParameter rendererForBounds:drawParameter->_bounds]; + BOOL renderedWithGraphicsRenderer = NO; - // Fill background - if (backgroundColor != nil) { - [backgroundColor setFill]; - UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy); + if (AS_AVAILABLE_IOS_TVOS(10, 10)) { + if (ASActivateExperimentalFeature(ASExperimentalTextDrawing)) { + renderedWithGraphicsRenderer = YES; + UIGraphicsImageRenderer *graphicsRenderer = [[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(drawParameter->_bounds.size.width, drawParameter->_bounds.size.height)]; + result = [graphicsRenderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) { + CGContextRef context = rendererContext.CGContext; + ASDisplayNodeAssert(context, @"This is no good without a context."); + + CGContextSaveGState(context); + CGContextTranslateCTM(context, textContainerInsets.left, textContainerInsets.top); + + // Fill background + if (backgroundColor != nil) { + [backgroundColor setFill]; + UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy); + } + + // Draw text + [renderer drawInContext:context bounds:drawParameter->_bounds]; + CGContextRestoreGState(context); + }]; + } } - // Draw text - [renderer drawInContext:context bounds:bounds]; - CGContextRestoreGState(context); + if (!renderedWithGraphicsRenderer) { + UIGraphicsBeginImageContextWithOptions(CGSizeMake(drawParameter->_bounds.size.width, drawParameter->_bounds.size.height), drawParameter->_opaque, drawParameter->_contentScale); + + CGContextRef context = UIGraphicsGetCurrentContext(); + ASDisplayNodeAssert(context, @"This is no good without a context."); + + CGContextSaveGState(context); + CGContextTranslateCTM(context, textContainerInsets.left, textContainerInsets.top); + + // Fill background + if (backgroundColor != nil) { + [backgroundColor setFill]; + UIRectFillUsingBlendMode(CGContextGetClipBoundingBox(context), kCGBlendModeCopy); + } + + // Draw text + [renderer drawInContext:context bounds:drawParameter->_bounds]; + CGContextRestoreGState(context); + + result = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + } + + return result; } #pragma mark - Attributes diff --git a/Source/Details/_ASDisplayLayer.h b/Source/Details/_ASDisplayLayer.h index e69ab71fd..1066a36f1 100644 --- a/Source/Details/_ASDisplayLayer.h +++ b/Source/Details/_ASDisplayLayer.h @@ -112,7 +112,7 @@ NS_ASSUME_NONNULL_BEGIN @summary Delegate override to provide new layer contents as a UIImage. @param parameters An object describing all of the properties you need to draw. Return this from -drawParametersForAsyncLayer: @param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return. - @return A UIImage with contents that are ready to display on the main thread. Make sure that the image is already decoded before returning it here. + @return A UIImage (backed by a CGImage) with contents that are ready to display on the main thread. Make sure that the image is already decoded before returning it here. */ + (UIImage *)displayWithParameters:(nullable id)parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock; diff --git a/Tests/ASConfigurationTests.mm b/Tests/ASConfigurationTests.mm index a5f37c11c..1a4553200 100644 --- a/Tests/ASConfigurationTests.mm +++ b/Tests/ASConfigurationTests.mm @@ -31,7 +31,8 @@ ASExperimentalSkipAccessibilityWait, ASExperimentalNewDefaultCellLayoutMode, ASExperimentalDispatchApply, - ASExperimentalImageDownloaderPriority + ASExperimentalImageDownloaderPriority, + ASExperimentalTextDrawing }; @interface ASConfigurationTests : ASTestCase @@ -57,7 +58,8 @@ + (NSArray *)names { @"exp_skip_a11y_wait", @"exp_new_default_cell_layout_mode", @"exp_dispatch_apply", - @"exp_image_downloader_priority" + @"exp_image_downloader_priority", + @"exp_text_drawing" ]; }