Fabric/Text: textlayoutmanager

Summary:
TextLayoutManager measures and renders text using iOS specific APIs (CoreText & TextKit).
By desing, only this module should contain platfrom-specific text functionality.

Reviewed By: mdvacca

Differential Revision: D7751852

fbshipit-source-id: fd6e1907df617fe5a4479ea08f207946765b3a45
This commit is contained in:
Valentin Shergin 2018-05-07 18:58:06 -07:00 committed by Facebook Github Bot
parent 62576bcb78
commit 05890a5942
15 changed files with 1027 additions and 0 deletions

View File

@ -185,6 +185,15 @@ Pod::Spec.new do |s|
sss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ReactCommon\" \"$(PODS_ROOT)/Folly\"" }
end
ss.subspec "textlayoutmanager" do |sss|
sss.dependency "Folly", folly_version
sss.compiler_flags = folly_compiler_flags
sss.source_files = "ReactCommon/fabric/textlayoutmanager/**/*.{cpp,h}"
sss.exclude_files = "**/tests/*"
sss.header_dir = "fabric/textlayoutmanager"
sss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ReactCommon\" \"$(PODS_ROOT)/Folly\"" }
end
ss.subspec "uimanager" do |sss|
sss.dependency "Folly", folly_version
sss.compiler_flags = folly_compiler_flags

View File

@ -0,0 +1,88 @@
load("//configurations/buck/apple:flag_defs.bzl", "get_debug_preprocessor_flags", "get_fbobjc_enable_exception_lang_compiler_flags")
load("//ReactNative:DEFS.bzl", "IS_OSS_BUILD", "react_native_xplat_target", "rn_xplat_cxx_library", "get_apple_inspector_flags", "APPLE")
APPLE_COMPILER_FLAGS = []
if not IS_OSS_BUILD:
load("@xplat//configurations/buck/apple:flag_defs.bzl", "get_static_library_ios_flags", "flags")
APPLE_COMPILER_FLAGS = flags.get_flag_value(get_static_library_ios_flags(), 'compiler_flags')
rn_xplat_cxx_library(
name = "textlayoutmanager",
srcs = glob(
[
"**/*.cpp",
"**/*.mm",
],
excludes = glob(["tests/**/*.cpp"]),
),
headers = glob(
["**/*.h"],
excludes = glob(["tests/**/*.h"]),
),
header_namespace = "",
exported_headers = subdir_glob(
[
("", "*.h"),
],
prefix = "fabric/textlayoutmanager",
),
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
fbobjc_compiler_flags = APPLE_COMPILER_FLAGS,
fbobjc_preprocessor_flags = get_debug_preprocessor_flags() + get_apple_inspector_flags(),
fbobjc_tests = [
":tests",
],
force_static = True,
macosx_tests_override = [],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/QuartzCore.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
],
lang_compiler_flags = get_fbobjc_enable_exception_lang_compiler_flags(),
preprocessor_flags = get_debug_preprocessor_flags() + [
"-DLOG_TAG=\"ReactNative\"",
"-DWITH_FBSYSTRACE=1",
],
tests = [],
visibility = ["PUBLIC"],
deps = [
"xplat//fbsystrace:fbsystrace",
"xplat//folly:headers_only",
"xplat//folly:memory",
"xplat//folly:molly",
"xplat//third-party/glog:glog",
react_native_xplat_target("fabric/attributedstring:attributedstring"),
react_native_xplat_target("fabric/core:core"),
react_native_xplat_target("fabric/debug:debug"),
react_native_xplat_target("fabric/graphics:graphics"),
],
)
if not IS_OSS_BUILD:
load("@xplat//build_defs:fb_xplat_cxx_test.bzl", "fb_xplat_cxx_test")
fb_xplat_cxx_test(
name = "tests",
srcs = glob(["tests/**/*.cpp"]),
headers = glob(["tests/**/*.h"]),
contacts = ["oncall+react_native@xmail.facebook.com"],
compiler_flags = [
"-fexceptions",
"-frtti",
"-std=c++14",
"-Wall",
],
platforms = APPLE,
deps = [
"xplat//folly:molly",
"xplat//third-party/gmock:gtest",
":textlayoutmanager",
],
)

View File

@ -0,0 +1,20 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
@interface NSTextStorage (FontScaling)
- (void)scaleFontSizeToFitSize:(CGSize)size
minimumFontSize:(CGFloat)minimumFontSize
maximumFontSize:(CGFloat)maximumFontSize;
- (void)scaleFontSizeWithRatio:(CGFloat)ratio
minimumFontSize:(CGFloat)minimumFontSize
maximumFontSize:(CGFloat)maximumFontSize;
@end

View File

@ -0,0 +1,137 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "NSTextStorage+FontScaling.h"
typedef NS_OPTIONS(NSInteger, RCTTextSizeComparisonOptions) {
RCTTextSizeComparisonSmaller = 1 << 0,
RCTTextSizeComparisonLarger = 1 << 1,
RCTTextSizeComparisonWithinRange = 1 << 2,
};
@implementation NSTextStorage (FontScaling)
- (void)scaleFontSizeToFitSize:(CGSize)size
minimumFontSize:(CGFloat)minimumFontSize
maximumFontSize:(CGFloat)maximumFontSize
{
CGFloat bottomRatio = 1.0/128.0;
CGFloat topRatio = 128.0;
CGFloat ratio = 1.0;
NSAttributedString *originalAttributedString = [self copy];
CGFloat lastRatioWhichFits = 0.02;
while (true) {
[self scaleFontSizeWithRatio:ratio
minimumFontSize:minimumFontSize
maximumFontSize:maximumFontSize];
RCTTextSizeComparisonOptions comparsion =
[self compareToSize:size thresholdRatio:0.01];
if (
(comparsion & RCTTextSizeComparisonWithinRange) &&
(comparsion & RCTTextSizeComparisonSmaller)
) {
return;
} else if (comparsion & RCTTextSizeComparisonSmaller) {
bottomRatio = ratio;
lastRatioWhichFits = ratio;
} else {
topRatio = ratio;
}
ratio = (topRatio + bottomRatio) / 2.0;
CGFloat kRatioThreshold = 0.005;
if (
ABS(topRatio - bottomRatio) < kRatioThreshold ||
ABS(topRatio - ratio) < kRatioThreshold ||
ABS(bottomRatio - ratio) < kRatioThreshold
) {
[self replaceCharactersInRange:(NSRange){0, self.length}
withAttributedString:originalAttributedString];
[self scaleFontSizeWithRatio:lastRatioWhichFits
minimumFontSize:minimumFontSize
maximumFontSize:maximumFontSize];
return;
}
[self replaceCharactersInRange:(NSRange){0, self.length}
withAttributedString:originalAttributedString];
}
}
- (RCTTextSizeComparisonOptions)compareToSize:(CGSize)size thresholdRatio:(CGFloat)thresholdRatio
{
NSLayoutManager *layoutManager = self.layoutManagers.firstObject;
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
[layoutManager ensureLayoutForTextContainer:textContainer];
// Does it fit the text container?
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
NSRange truncatedGlyphRange = [layoutManager truncatedGlyphRangeInLineFragmentForGlyphAtIndex:glyphRange.length - 1];
if (truncatedGlyphRange.location != NSNotFound) {
return RCTTextSizeComparisonLarger;
}
CGSize measuredSize = [layoutManager usedRectForTextContainer:textContainer].size;
// Does it fit the size?
BOOL fitsSize =
size.width >= measuredSize.width &&
size.height >= measuredSize.height;
CGSize thresholdSize = (CGSize){
size.width * thresholdRatio,
size.height * thresholdRatio,
};
RCTTextSizeComparisonOptions result = 0;
result |= (fitsSize) ? RCTTextSizeComparisonSmaller : RCTTextSizeComparisonLarger;
if (ABS(measuredSize.width - size.width) < thresholdSize.width) {
result = result | RCTTextSizeComparisonWithinRange;
}
return result;
}
- (void)scaleFontSizeWithRatio:(CGFloat)ratio
minimumFontSize:(CGFloat)minimumFontSize
maximumFontSize:(CGFloat)maximumFontSize
{
[self beginEditing];
[self enumerateAttribute:NSFontAttributeName
inRange:(NSRange){0, self.length}
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:
^(UIFont *_Nullable font, NSRange range, BOOL *_Nonnull stop) {
if (!font) {
return;
}
CGFloat fontSize = MAX(MIN(font.pointSize * ratio, maximumFontSize), minimumFontSize);
[self addAttribute:NSFontAttributeName
value:[font fontWithSize:fontSize]
range:range];
}
];
[self endEditing];
}
@end

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#include <fabric/attributedstring/AttributedString.h>
#include <fabric/attributedstring/TextAttributes.h>
NS_ASSUME_NONNULL_BEGIN
NSString *const RCTAttributedStringIsHighlightedAttributeName = @"IsHighlighted";
NSString *const RCTAttributedStringReactTagAttributeName = @"ReactTag";
/**
* Constructs ready-to-render `NSAttributedString` by given `AttributedString`.
*/
NSAttributedString *RCTNSAttributedStringFromAttributedString(const facebook::react::AttributedString &attributedString);
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,227 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTAttributedTextUtils.h"
#include <fabric/core/LayoutableShadowNode.h>
#include <fabric/textlayoutmanager/RCTFontProperties.h>
#include <fabric/textlayoutmanager/RCTFontUtils.h>
#include <fabric/textlayoutmanager/RCTTextPrimitivesConversions.h>
inline static UIFont *RCTEffectiveFontFromTextAttributes(const TextAttributes &textAttributes) {
NSString *fontFamily = [NSString stringWithCString:textAttributes.fontFamily.c_str()
encoding:NSASCIIStringEncoding];
RCTFontProperties fontProperties;
fontProperties.family = fontFamily;
fontProperties.size = textAttributes.fontSize;
fontProperties.style = textAttributes.fontStyle.has_value() ? RCTFontStyleFromFontStyle(textAttributes.fontStyle.value()) : RCTFontStyleUndefined;
fontProperties.variant = textAttributes.fontVariant.has_value() ? RCTFontVariantFromFontVariant(textAttributes.fontVariant.value()) : RCTFontVariantDefault;
fontProperties.weight = textAttributes.fontWeight.has_value() ? CGFloat(textAttributes.fontWeight.value()) : NAN;
fontProperties.sizeMultiplier = textAttributes.fontSizeMultiplier;
return RCTFontWithFontProperties(fontProperties);
}
inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const TextAttributes &textAttributes) {
return textAttributes.allowFontScaling.value_or(true) && !isnan(textAttributes.fontSizeMultiplier) ? textAttributes.fontSizeMultiplier : 1.0;
}
inline static UIColor *RCTEffectiveForegroundColorFromTextAttributes(const TextAttributes &textAttributes) {
UIColor *effectiveForegroundColor = RCTUIColorFromSharedColor(textAttributes.foregroundColor) ?: [UIColor blackColor];
if (!isnan(textAttributes.opacity)) {
effectiveForegroundColor =
[effectiveForegroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveForegroundColor.CGColor) * textAttributes.opacity];
}
return effectiveForegroundColor;
}
inline static UIColor *RCTEffectiveBackgroundColorFromTextAttributes(const TextAttributes &textAttributes) {
UIColor *effectiveBackgroundColor = RCTUIColorFromSharedColor(textAttributes.backgroundColor);
if (effectiveBackgroundColor && !isnan(textAttributes.opacity)) {
effectiveBackgroundColor =
[effectiveBackgroundColor colorWithAlphaComponent:CGColorGetAlpha(effectiveBackgroundColor.CGColor) * textAttributes.opacity];
}
return effectiveBackgroundColor ?: [UIColor clearColor];
}
static NSDictionary<NSAttributedStringKey, id> *RCTNSTextAttributesFromTextAttributes(const TextAttributes &textAttributes) {
NSMutableDictionary<NSAttributedStringKey, id> *attributes =
[NSMutableDictionary dictionaryWithCapacity:10];
// Font
UIFont *font = RCTEffectiveFontFromTextAttributes(textAttributes);
if (font) {
attributes[NSFontAttributeName] = font;
}
// Colors
UIColor *effectiveForegroundColor = RCTEffectiveForegroundColorFromTextAttributes(textAttributes);
if (textAttributes.foregroundColor || !isnan(textAttributes.opacity)) {
attributes[NSForegroundColorAttributeName] = effectiveForegroundColor;
}
if (textAttributes.backgroundColor || !isnan(textAttributes.opacity)) {
attributes[NSBackgroundColorAttributeName] = RCTEffectiveBackgroundColorFromTextAttributes(textAttributes);
}
// Kerning
if (!isnan(textAttributes.letterSpacing)) {
attributes[NSKernAttributeName] = @(textAttributes.letterSpacing);
}
// Paragraph Style
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
BOOL isParagraphStyleUsed = NO;
if (textAttributes.alignment.has_value()) {
TextAlignment textAlignment = textAttributes.alignment.value_or(TextAlignment::Natural);
if (textAttributes.layoutDirection.value_or(LayoutDirection::LeftToRight) == LayoutDirection::RightToLeft) {
if (textAlignment == TextAlignment::Right) {
textAlignment = TextAlignment::Left;
} else if (textAlignment == TextAlignment::Left) {
textAlignment = TextAlignment::Right;
}
}
paragraphStyle.alignment =
RCTNSTextAlignmentFromTextAlignment(textAlignment);
isParagraphStyleUsed = YES;
}
if (textAttributes.baseWritingDirection.has_value()) {
paragraphStyle.baseWritingDirection =
RCTNSWritingDirectionFromWritingDirection(textAttributes.baseWritingDirection.value());
isParagraphStyleUsed = YES;
}
if (!isnan(textAttributes.lineHeight)) {
CGFloat lineHeight =
textAttributes.lineHeight * RCTEffectiveFontSizeMultiplierFromTextAttributes(textAttributes);
paragraphStyle.minimumLineHeight = lineHeight;
paragraphStyle.maximumLineHeight = lineHeight;
isParagraphStyleUsed = YES;
}
if (isParagraphStyleUsed) {
attributes[NSParagraphStyleAttributeName] = paragraphStyle;
}
// Decoration
if (textAttributes.textDecorationLineType.value_or(TextDecorationLineType::None) != TextDecorationLineType::None) {
auto textDecorationLineType = textAttributes.textDecorationLineType.value();
NSUnderlineStyle style =
RCTNSUnderlineStyleFromStyleAndPattern(
textAttributes.textDecorationLineStyle.value_or(TextDecorationLineStyle::Single),
textAttributes.textDecorationLinePattern.value_or(TextDecorationLinePattern::Solid)
);
UIColor *textDecorationColor = RCTUIColorFromSharedColor(textAttributes.textDecorationColor);
// Underline
if (textDecorationLineType == TextDecorationLineType::Underline ||
textDecorationLineType == TextDecorationLineType::UnderlineStrikethrough) {
attributes[NSUnderlineStyleAttributeName] = @(style);
if (textDecorationColor) {
attributes[NSUnderlineColorAttributeName] = textDecorationColor;
}
}
// Strikethrough
if (textDecorationLineType == TextDecorationLineType::Strikethrough ||
textDecorationLineType == TextDecorationLineType::UnderlineStrikethrough) {
attributes[NSStrikethroughStyleAttributeName] = @(style);
if (textDecorationColor) {
attributes[NSStrikethroughColorAttributeName] = textDecorationColor;
}
}
}
// Shadow
if (textAttributes.textShadowOffset.has_value()) {
auto textShadowOffset = textAttributes.textShadowOffset.value();
NSShadow *shadow = [NSShadow new];
shadow.shadowOffset = CGSize {textShadowOffset.x, textShadowOffset.y};
shadow.shadowBlurRadius = textAttributes.textShadowRadius;
shadow.shadowColor = RCTUIColorFromSharedColor(textAttributes.textShadowColor);
attributes[NSShadowAttributeName] = shadow;
}
// Special
if (textAttributes.isHighlighted) {
attributes[RCTAttributedStringIsHighlightedAttributeName] = @YES;
}
return [attributes copy];
}
NSAttributedString *RCTNSAttributedStringFromAttributedString(const AttributedString &attributedString) {
NSMutableAttributedString *nsAttributedString = [[NSMutableAttributedString alloc] init];
[nsAttributedString beginEditing];
for (auto fragment : attributedString.getFragments()) {
NSAttributedString *nsAttributedStringFragment;
SharedLayoutableShadowNode layoutableShadowNode =
std::dynamic_pointer_cast<const LayoutableShadowNode>(fragment.shadowNode);
if (layoutableShadowNode) {
auto layoutMetrics = layoutableShadowNode->getLayoutMetrics();
CGRect bounds = {
.origin = {
.x = layoutMetrics.frame.origin.x,
.y = layoutMetrics.frame.origin.y
},
.size = {
.width = layoutMetrics.frame.size.width,
.height = layoutMetrics.frame.size.height
}
};
NSTextAttachment *attachment = [NSTextAttachment new];
attachment.bounds = bounds;
nsAttributedStringFragment = [NSAttributedString attributedStringWithAttachment:attachment];
} else {
NSString *string =
[NSString stringWithCString:fragment.string.c_str()
encoding:NSASCIIStringEncoding];
nsAttributedStringFragment =
[[NSAttributedString alloc] initWithString:string
attributes:RCTNSTextAttributesFromTextAttributes(fragment.textAttributes)];
}
NSMutableAttributedString *nsMutableAttributedStringFragment =
[[NSMutableAttributedString alloc] initWithAttributedString:nsAttributedStringFragment];
if (fragment.shadowNode) {
NSDictionary<NSAttributedStringKey, id> *additionalTextAttributes = @{
RCTAttributedStringReactTagAttributeName: @(fragment.shadowNode->getTag())
};
[nsMutableAttributedStringFragment setAttributes:additionalTextAttributes
range:NSMakeRange(0, nsMutableAttributedStringFragment.length)];
}
[nsAttributedString appendAttributedString:nsMutableAttributedStringFragment];
}
[nsAttributedString endEditing];
return nsAttributedString;
}

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, RCTFontStyle) {
RCTFontStyleUndefined = -1,
RCTFontStyleNormal,
RCTFontStyleItalic,
RCTFontStyleOblique,
};
typedef NS_OPTIONS(NSInteger, RCTFontVariant) {
RCTFontVariantUndefined = -1,
RCTFontVariantDefault = 0,
RCTFontVariantSmallCaps = 1 << 1,
RCTFontVariantOldstyleNums = 1 << 2,
RCTFontVariantLiningNums = 1 << 3,
RCTFontVariantTabularNums = 1 << 4,
RCTFontVariantProportionalNums = 1 << 5,
};
struct RCTFontProperties {
NSString *family;
CGFloat size;
UIFontWeight weight;
RCTFontStyle style;
RCTFontVariant variant;
CGFloat sizeMultiplier;
};
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <fabric/textlayoutmanager/RCTFontProperties.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Returns UIFont instance corresponded to given font properties.
*/
UIFont *RCTFontWithFontProperties(RCTFontProperties fontProperties);
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,153 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTFontUtils.h"
#import <mutex>
static RCTFontProperties RCTDefaultFontProperties() {
static RCTFontProperties defaultFontProperties;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultFontProperties.size = 14;
defaultFontProperties.family =
[UIFont systemFontOfSize:defaultFontProperties.size].familyName;
defaultFontProperties.style = RCTFontStyleNormal;
defaultFontProperties.variant = RCTFontVariantDefault;
defaultFontProperties.sizeMultiplier = 1.0;
});
return defaultFontProperties;
}
static RCTFontProperties RCTResolveFontProperties(RCTFontProperties fontProperties) {
RCTFontProperties defaultFontProperties = RCTDefaultFontProperties();
fontProperties.family = fontProperties.family.length && ![fontProperties.family isEqualToString:@"System"] ? fontProperties.family : defaultFontProperties.family;
fontProperties.size = !isnan(fontProperties.size) ? fontProperties.size : defaultFontProperties.size;
fontProperties.weight = !isnan(fontProperties.weight) ? fontProperties.weight : defaultFontProperties.weight;
fontProperties.style = fontProperties.style != RCTFontStyleUndefined ? fontProperties.style : defaultFontProperties.style;
fontProperties.variant = fontProperties.variant != RCTFontVariantUndefined ? fontProperties.variant : defaultFontProperties.variant;
return fontProperties;
}
static UIFontWeight RCTGetFontWeight(UIFont *font) {
NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
return [traits[UIFontWeightTrait] doubleValue];
}
static RCTFontStyle RCTGetFontStyle(UIFont *font) {
NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
UIFontDescriptorSymbolicTraits symbolicTraits = [traits[UIFontSymbolicTrait] unsignedIntValue];
if (symbolicTraits & UIFontDescriptorTraitItalic) {
return RCTFontStyleItalic;
}
return RCTFontStyleNormal;
}
static NSArray *RCTFontFeatures(RCTFontVariant fontVariant) {
// FIXME:
return @[];
}
static UIFont *RCTDefaultFontWithFontProperties(RCTFontProperties fontProperties) {
static NSCache *fontCache;
static std::mutex fontCacheMutex;
NSString *cacheKey = [NSString stringWithFormat:@"%.1f/%.2f", fontProperties.size, fontProperties.weight];
UIFont *font;
{
std::lock_guard<std::mutex> lock(fontCacheMutex);
if (!fontCache) {
fontCache = [NSCache new];
}
font = [fontCache objectForKey:cacheKey];
}
if (!font) {
font = [UIFont systemFontOfSize:fontProperties.size
weight:fontProperties.weight];
if (fontProperties.variant == RCTFontStyleItalic) {
UIFontDescriptor *fontDescriptor = [font fontDescriptor];
UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits;
symbolicTraits |= UIFontDescriptorTraitItalic;
fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits];
font = [UIFont fontWithDescriptor:fontDescriptor size:fontProperties.size];
}
{
std::lock_guard<std::mutex> lock(fontCacheMutex);
[fontCache setObject:font forKey:cacheKey];
}
}
return font;
}
UIFont *RCTFontWithFontProperties(RCTFontProperties fontProperties) {
RCTFontProperties defaultFontProperties = RCTDefaultFontProperties();
fontProperties = RCTResolveFontProperties(fontProperties);
CGFloat effectiveFontSize = fontProperties.sizeMultiplier * fontProperties.size;
UIFont *font;
if ([fontProperties.family isEqualToString:defaultFontProperties.family]) {
// Handle system font as special case. This ensures that we preserve
// the specific metrics of the standard system font as closely as possible.
font = RCTDefaultFontWithFontProperties(fontProperties);
} else {
NSArray<NSString *> *fontNames =
[UIFont fontNamesForFamilyName:fontProperties.family];
if (fontNames.count == 0) {
// Gracefully handle being given a font name rather than font family, for
// example: "Helvetica Light Oblique" rather than just "Helvetica".
font = [UIFont fontWithName:fontProperties.family size:effectiveFontSize];
if (!font) {
// Failback to system font.
font = [UIFont systemFontOfSize:effectiveFontSize weight:fontProperties.weight];
}
} else {
// Get the closest font that matches the given weight for the fontFamily
CGFloat closestWeight = INFINITY;
for (NSString *name in fontNames) {
UIFont *fontMatch = [UIFont fontWithName:name size:effectiveFontSize];
if (RCTGetFontStyle(fontMatch) != fontProperties.style) {
continue;
}
CGFloat testWeight = RCTGetFontWeight(fontMatch);
if (ABS(testWeight - fontProperties.weight) < ABS(closestWeight - fontProperties.weight)) {
font = fontMatch;
closestWeight = testWeight;
}
}
if (!font) {
// If we still don't have a match at least return the first font in the fontFamily
// This is to support built-in font Zapfino and other custom single font families like Impact
font = [UIFont fontWithName:fontNames[0] size:effectiveFontSize];
}
}
}
// Apply font variants to font object.
if (fontProperties.variant != RCTFontVariantDefault) {
NSArray *fontFeatures = RCTFontFeatures(fontProperties.variant);
UIFontDescriptor *fontDescriptor =
[font.fontDescriptor fontDescriptorByAddingAttributes:@{UIFontDescriptorFeatureSettingsAttribute: fontFeatures}];
font = [UIFont fontWithDescriptor:fontDescriptor size:effectiveFontSize];
}
return font;
}

View File

@ -0,0 +1,32 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <fabric/core/LayoutConstraints.h>
#import <fabric/graphics/Geometry.h>
#import <fabric/attributedstring/AttributedString.h>
#import <fabric/attributedstring/ParagraphAttributes.h>
NS_ASSUME_NONNULL_BEGIN
/**
* iOS-specific TextLayoutManager
*/
@interface RCTTextLayoutManager : NSObject
- (facebook::react::Size)measureWithAttributedString:(facebook::react::AttributedString)attributedString
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
layoutConstraints:(facebook::react::LayoutConstraints)layoutConstraints;
- (void)drawAttributedString:(facebook::react::AttributedString)attributedString
paragraphAttributes:(facebook::react::ParagraphAttributes)paragraphAttributes
frame:(CGRect)frame;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,97 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTTextLayoutManager.h"
#import "NSTextStorage+FontScaling.h"
#import "RCTAttributedTextUtils.h"
using namespace facebook::react;
@implementation RCTTextLayoutManager
static NSLineBreakMode RCTNSLineBreakModeFromWritingDirection(EllipsizeMode ellipsizeMode) {
switch (ellipsizeMode) {
case EllipsizeMode::Clip: return NSLineBreakByClipping;
case EllipsizeMode::Head: return NSLineBreakByTruncatingHead;
case EllipsizeMode::Tail: return NSLineBreakByTruncatingTail;
case EllipsizeMode::Middle: return NSLineBreakByTruncatingMiddle;
}
}
- (facebook::react::Size)measureWithAttributedString:(AttributedString)attributedString
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
layoutConstraints:(LayoutConstraints)layoutConstraints
{
CGSize maximumSize = CGSize {layoutConstraints.maximumSize.width, layoutConstraints.maximumSize.height};
NSTextStorage *textStorage =
[self _textStorageAndLayoutManagerWithAttributesString:RCTNSAttributedStringFromAttributedString(attributedString)
paragraphAttributes:paragraphAttributes
size:maximumSize];
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
[layoutManager ensureLayoutForTextContainer:textContainer];
CGSize size = [layoutManager usedRectForTextContainer:textContainer].size;
size = (CGSize){
MIN(size.width, maximumSize.width),
MIN(size.height, maximumSize.height)
};
return facebook::react::Size {size.width, size.height};
}
- (void)drawAttributedString:(AttributedString)attributedString
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
frame:(CGRect)frame
{
NSTextStorage *textStorage =
[self _textStorageAndLayoutManagerWithAttributesString:RCTNSAttributedStringFromAttributedString(attributedString)
paragraphAttributes:paragraphAttributes
size:frame.size];
NSLayoutManager *layoutManager = textStorage.layoutManagers.firstObject;
NSTextContainer *textContainer = layoutManager.textContainers.firstObject;
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:frame.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:frame.origin];
}
- (NSTextStorage *)_textStorageAndLayoutManagerWithAttributesString:(NSAttributedString *)attributedString
paragraphAttributes:(ParagraphAttributes)paragraphAttributes
size:(CGSize)size
{
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:size];
textContainer.lineFragmentPadding = 0.0; // Note, the default value is 5.
textContainer.lineBreakMode =
paragraphAttributes.maximumNumberOfLines > 0 ? RCTNSLineBreakModeFromWritingDirection(paragraphAttributes.ellipsizeMode) : NSLineBreakByClipping;
textContainer.maximumNumberOfLines = paragraphAttributes.maximumNumberOfLines;
NSLayoutManager *layoutManager = [NSLayoutManager new];
[layoutManager addTextContainer:textContainer];
NSTextStorage *textStorage =
[[NSTextStorage alloc] initWithAttributedString:attributedString];
[textStorage addLayoutManager:layoutManager];
if (paragraphAttributes.adjustsFontSizeToFit) {
CGFloat minimumFontSize = !isnan(paragraphAttributes.minimumFontSize) ? paragraphAttributes.minimumFontSize : 4.0;
CGFloat maximumFontSize = !isnan(paragraphAttributes.maximumFontSize) ? paragraphAttributes.maximumFontSize : 96.0;
[textStorage scaleFontSizeToFitSize:size
minimumFontSize:minimumFontSize
maximumFontSize:maximumFontSize];
}
return textStorage;
}
@end

View File

@ -0,0 +1,75 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#include <fabric/textlayoutmanager/RCTFontProperties.h>
#include <fabric/textlayoutmanager/RCTFontUtils.h>
using namespace facebook::react;
inline static NSTextAlignment RCTNSTextAlignmentFromTextAlignment(TextAlignment textAlignment) {
switch (textAlignment) {
case TextAlignment::Natural: return NSTextAlignmentNatural;
case TextAlignment::Left: return NSTextAlignmentLeft;
case TextAlignment::Right: return NSTextAlignmentRight;
case TextAlignment::Center: return NSTextAlignmentCenter;
case TextAlignment::Justified: return NSTextAlignmentJustified;
}
}
inline static NSWritingDirection RCTNSWritingDirectionFromWritingDirection(WritingDirection writingDirection) {
switch (writingDirection) {
case WritingDirection::Natural: return NSWritingDirectionNatural;
case WritingDirection::LeftToRight: return NSWritingDirectionLeftToRight;
case WritingDirection::RightToLeft: return NSWritingDirectionRightToLeft;
}
}
inline static RCTFontStyle RCTFontStyleFromFontStyle(FontStyle fontStyle) {
switch (fontStyle) {
case FontStyle::Normal: return RCTFontStyleNormal;
case FontStyle::Italic: return RCTFontStyleItalic;
case FontStyle::Oblique: return RCTFontStyleOblique;
}
}
inline static RCTFontVariant RCTFontVariantFromFontVariant(FontVariant fontVariant) {
return (RCTFontVariant)fontVariant;
}
inline static NSUnderlineStyle RCTNSUnderlineStyleFromStyleAndPattern(TextDecorationLineStyle textDecorationLineStyle, TextDecorationLinePattern textDecorationLinePattern) {
NSUnderlineStyle style = NSUnderlineStyleNone;
switch (textDecorationLineStyle) {
case TextDecorationLineStyle::Single:
style = NSUnderlineStyle(style | NSUnderlineStyleSingle); break;
case TextDecorationLineStyle::Thick:
style = NSUnderlineStyle(style | NSUnderlineStyleThick); break;
case TextDecorationLineStyle::Double:
style = NSUnderlineStyle(style | NSUnderlineStyleDouble); break;
}
switch (textDecorationLinePattern) {
case TextDecorationLinePattern::Solid:
style = NSUnderlineStyle(style | NSUnderlinePatternSolid); break;
case TextDecorationLinePattern::Dash:
style = NSUnderlineStyle(style | NSUnderlinePatternDash); break;
case TextDecorationLinePattern::Dot:
style = NSUnderlineStyle(style | NSUnderlinePatternDot); break;
case TextDecorationLinePattern::DashDot:
style = NSUnderlineStyle(style | NSUnderlinePatternDashDot); break;
case TextDecorationLinePattern::DashDotDot:
style = NSUnderlineStyle(style | NSUnderlinePatternDashDotDot); break;
}
return style;
}
inline static UIColor *RCTUIColorFromSharedColor(const SharedColor &color) {
return color ? [UIColor colorWithCGColor:color.get()] : nil;
}

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <fabric/attributedstring/AttributedString.h>
#include <fabric/attributedstring/ParagraphAttributes.h>
#include <fabric/core/LayoutConstraints.h>
namespace facebook {
namespace react {
class TextLayoutManager;
using SharedTextLayoutManager = std::shared_ptr<const TextLayoutManager>;
/*
* Cross platform facade for iOS-specific RCTTTextLayoutManager.
*/
class TextLayoutManager {
public:
TextLayoutManager();
~TextLayoutManager();
/*
* Measures `attributedString` using native text rendering infrastructure.
*/
Size measure(
AttributedString attributedString,
ParagraphAttributes paragraphAttributes,
LayoutConstraints layoutConstraints
) const;
/*
* Returns an opaque pointer to platform-specific TextLayoutManager.
* Is used on a native views layer to delegate text rendering to the manager.
*/
void *getNativeTextLayoutManager() const;
private:
void *self_;
};
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TextLayoutManager.h"
#import "RCTTextLayoutManager.h"
namespace facebook {
namespace react {
TextLayoutManager::TextLayoutManager() {
self_ = (__bridge_retained void *)[RCTTextLayoutManager new];
}
TextLayoutManager::~TextLayoutManager() {
CFRelease(self_);
self_ = nullptr;
}
void *TextLayoutManager::getNativeTextLayoutManager() const {
return self_;
}
Size TextLayoutManager::measure(
AttributedString attributedString,
ParagraphAttributes paragraphAttributes,
LayoutConstraints layoutConstraints
) const {
RCTTextLayoutManager *textLayoutManager = (__bridge RCTTextLayoutManager *)self_;
return [textLayoutManager measureWithAttributedString:attributedString
paragraphAttributes:paragraphAttributes
layoutConstraints:layoutConstraints];
}
} // namespace react
} // namespace facebook

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <memory>
#include <gtest/gtest.h>
#include <fabric/textlayoutmanager/TextLayoutManager.h>
using namespace facebook::react;
TEST(TextLayoutManagerTest, testSomething) {
// TODO:
}