138 lines
4.0 KiB
Objective-C
138 lines
4.0 KiB
Objective-C
/**
|
|
* 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
|