123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- /*
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
- #import "RCTConvert+ART.h"
- #import <React/RCTFont.h>
- #import <React/RCTLog.h>
- #import <React/ARTLinearGradient.h>
- #import <React/ARTPattern.h>
- #import <React/ARTRadialGradient.h>
- #import <React/ARTSolidColor.h>
- @implementation RCTConvert (ART)
- + (CGPathRef)CGPath:(id)json
- {
- NSArray *arr = [self NSNumberArray:json];
- NSUInteger count = [arr count];
- #define NEXT_VALUE [self double:arr[i++]]
- CGMutablePathRef path = CGPathCreateMutable();
- CGPathMoveToPoint(path, NULL, 0, 0);
- @try {
- NSUInteger i = 0;
- while (i < count) {
- NSUInteger type = [arr[i++] unsignedIntegerValue];
- switch (type) {
- case 0:
- CGPathMoveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE);
- break;
- case 1:
- CGPathCloseSubpath(path);
- break;
- case 2:
- CGPathAddLineToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE);
- break;
- case 3:
- CGPathAddCurveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE);
- break;
- case 4:
- CGPathAddArc(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE == 0);
- break;
- default:
- RCTLogError(@"Invalid CGPath type %llu at element %llu of %@", (unsigned long long)type, (unsigned long long)i, arr);
- CGPathRelease(path);
- return NULL;
- }
- }
- }
- @catch (NSException *exception) {
- RCTLogError(@"Invalid CGPath format: %@", arr);
- CGPathRelease(path);
- return NULL;
- }
- return (CGPathRef)CFAutorelease(path);
- }
- RCT_ENUM_CONVERTER(CTTextAlignment, (@{
- @"auto": @(kCTTextAlignmentNatural),
- @"left": @(kCTTextAlignmentLeft),
- @"center": @(kCTTextAlignmentCenter),
- @"right": @(kCTTextAlignmentRight),
- @"justify": @(kCTTextAlignmentJustified),
- }), kCTTextAlignmentNatural, integerValue)
- // This takes a tuple of text lines and a font to generate a CTLine for each text line.
- // This prepares everything for rendering a frame of text in ARTText.
- + (ARTTextFrame)ARTTextFrame:(id)json
- {
- NSDictionary *dict = [self NSDictionary:json];
- ARTTextFrame frame;
- frame.count = 0;
- NSArray *lines = [self NSArray:dict[@"lines"]];
- NSUInteger lineCount = [lines count];
- if (lineCount == 0) {
- return frame;
- }
- CTFontRef font = (__bridge CTFontRef)[self UIFont:dict[@"font"]];
- if (!font) {
- return frame;
- }
- // Create a dictionary for this font
- CFDictionaryRef attributes = (__bridge CFDictionaryRef)@{
- (NSString *)kCTFontAttributeName:(__bridge id)font,
- (NSString *)kCTForegroundColorFromContextAttributeName: @YES
- };
- // Set up text frame with font metrics
- CGFloat size = CTFontGetSize(font);
- frame.count = lineCount;
- frame.baseLine = size; // estimate base line
- frame.lineHeight = size * 1.1; // Base on ART canvas line height estimate
- frame.lines = malloc(sizeof(CTLineRef) * lineCount);
- frame.widths = malloc(sizeof(CGFloat) * lineCount);
- [lines enumerateObjectsUsingBlock:^(NSString *text, NSUInteger i, BOOL *stop) {
- CFStringRef string = (__bridge CFStringRef)text;
- CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
- CTLineRef line = CTLineCreateWithAttributedString(attrString);
- CFRelease(attrString);
- frame.lines[i] = line;
- frame.widths[i] = CTLineGetTypographicBounds(line, NULL, NULL, NULL);
- }];
- return frame;
- }
- + (ARTCGFloatArray)ARTCGFloatArray:(id)json
- {
- NSArray *arr = [self NSNumberArray:json];
- NSUInteger count = arr.count;
- ARTCGFloatArray array;
- array.count = count;
- array.array = NULL;
- if (count) {
- // Ideally, these arrays should already use the same memory layout.
- // In that case we shouldn't need this new malloc.
- array.array = malloc(sizeof(CGFloat) * count);
- for (NSUInteger i = 0; i < count; i++) {
- array.array[i] = [arr[i] doubleValue];
- }
- }
- return array;
- }
- + (ARTBrush *)ARTBrush:(id)json
- {
- NSArray *arr = [self NSArray:json];
- NSUInteger type = [self NSUInteger:arr.firstObject];
- switch (type) {
- case 0: // solid color
- // These are probably expensive allocations since it's often the same value.
- // We should memoize colors but look ups may be just as expensive.
- return [[ARTSolidColor alloc] initWithArray:arr];
- case 1: // linear gradient
- return [[ARTLinearGradient alloc] initWithArray:arr];
- case 2: // radial gradient
- return [[ARTRadialGradient alloc] initWithArray:arr];
- case 3: // pattern
- return [[ARTPattern alloc] initWithArray:arr];
- default:
- RCTLogError(@"Unknown brush type: %llu", (unsigned long long)type);
- return nil;
- }
- }
- + (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset
- {
- NSArray *arr = [self NSArray:json];
- if (arr.count < offset + 2) {
- RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(2 + offset), arr);
- return CGPointZero;
- }
- return (CGPoint){
- [self CGFloat:arr[offset]],
- [self CGFloat:arr[offset + 1]],
- };
- }
- + (CGRect)CGRect:(id)json offset:(NSUInteger)offset
- {
- NSArray *arr = [self NSArray:json];
- if (arr.count < offset + 4) {
- RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(4 + offset), arr);
- return CGRectZero;
- }
- return (CGRect){
- {[self CGFloat:arr[offset]], [self CGFloat:arr[offset + 1]]},
- {[self CGFloat:arr[offset + 2]], [self CGFloat:arr[offset + 3]]},
- };
- }
- + (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset
- {
- NSArray *arr = [self NSArray:json];
- if (arr.count < offset + 4) {
- RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)(4 + offset), arr);
- return NULL;
- }
- return [self CGColor:[arr subarrayWithRange:(NSRange){offset, 4}]];
- }
- + (CGGradientRef)CGGradient:(id)json offset:(NSUInteger)offset
- {
- NSArray *arr = [self NSArray:json];
- if (arr.count < offset) {
- RCTLogError(@"Too few elements in array (expected at least %llu): %@", (unsigned long long)offset, arr);
- return NULL;
- }
- arr = [arr subarrayWithRange:(NSRange){offset, arr.count - offset}];
- ARTCGFloatArray colorsAndOffsets = [self ARTCGFloatArray:arr];
- size_t stops = colorsAndOffsets.count / 5;
- CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
- CGGradientRef gradient = CGGradientCreateWithColorComponents(
- rgb,
- colorsAndOffsets.array,
- colorsAndOffsets.array + stops * 4,
- stops
- );
- CGColorSpaceRelease(rgb);
- free(colorsAndOffsets.array);
- return (CGGradientRef)CFAutorelease(gradient);
- }
- @end
|