mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-20 21:29:00 +00:00
adjust font size to make text fit within constrained size
# Conflicts: # AsyncDisplayKit/ASTextNode.mm
This commit is contained in:
parent
a4789f3524
commit
a920e353c6
@ -10,9 +10,9 @@
|
|||||||
@interface ASTextNode ()
|
@interface ASTextNode ()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@abstract The minimum scale that the textnode can apply to fit long words.
|
@abstract An array of descending scale factors that will be applied to this text node to try to make it fit within its constrained size
|
||||||
@default 0 (No scaling)
|
@default nil (no scaling)
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, assign) CGFloat minimumScaleFactor;
|
@property (nonatomic, copy) NSArray *pointSizeScaleFactors;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
#import "ASTextKitCoreTextAdditions.h"
|
#import "ASTextKitCoreTextAdditions.h"
|
||||||
#import "ASTextKitHelpers.h"
|
#import "ASTextKitHelpers.h"
|
||||||
|
#import "ASTextKitFontSizeAdjuster.h"
|
||||||
#import "ASTextKitRenderer.h"
|
#import "ASTextKitRenderer.h"
|
||||||
#import "ASTextKitRenderer+Positioning.h"
|
#import "ASTextKitRenderer+Positioning.h"
|
||||||
#import "ASTextKitShadower.h"
|
#import "ASTextKitShadower.h"
|
||||||
@ -78,6 +79,7 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation
|
|||||||
CGSize _constrainedSize;
|
CGSize _constrainedSize;
|
||||||
|
|
||||||
ASTextKitRenderer *_renderer;
|
ASTextKitRenderer *_renderer;
|
||||||
|
CGFloat _currentScaleFactor;
|
||||||
|
|
||||||
UILongPressGestureRecognizer *_longPressGestureRecognizer;
|
UILongPressGestureRecognizer *_longPressGestureRecognizer;
|
||||||
}
|
}
|
||||||
@ -242,7 +244,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
|||||||
.lineBreakMode = _truncationMode,
|
.lineBreakMode = _truncationMode,
|
||||||
.maximumNumberOfLines = _maximumNumberOfLines,
|
.maximumNumberOfLines = _maximumNumberOfLines,
|
||||||
.exclusionPaths = _exclusionPaths,
|
.exclusionPaths = _exclusionPaths,
|
||||||
.minimumScaleFactor = _minimumScaleFactor,
|
.pointSizeScaleFactors = _pointSizeScaleFactors,
|
||||||
|
.currentScaleFactor = _currentScaleFactor,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,6 +259,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
|||||||
// actually dealloc.
|
// actually dealloc.
|
||||||
__block ASTextKitRenderer *renderer = _renderer;
|
__block ASTextKitRenderer *renderer = _renderer;
|
||||||
ASPerformBlockOnBackgroundThread(^{
|
ASPerformBlockOnBackgroundThread(^{
|
||||||
|
// before we remove the renderer, take its scale factor so we set it when a new renderer is created
|
||||||
|
_currentScaleFactor = renderer.currentScaleFactor;
|
||||||
renderer = nil;
|
renderer = nil;
|
||||||
});
|
});
|
||||||
_renderer = nil;
|
_renderer = nil;
|
||||||
@ -1059,16 +1064,15 @@ static NSAttributedString *DefaultTruncationAttributedString()
|
|||||||
return visibleRange.length < _attributedString.length;
|
return visibleRange.length < _attributedString.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setMinimumScaleFactor:(CGFloat)minimumScaleFactor
|
- (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors
|
||||||
{
|
{
|
||||||
if (_minimumScaleFactor != minimumScaleFactor) {
|
if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors] == NO) {
|
||||||
_minimumScaleFactor = minimumScaleFactor;
|
_pointSizeScaleFactors = pointSizeScaleFactors;
|
||||||
[self _invalidateRenderer];
|
[self _invalidateRenderer];
|
||||||
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
|
ASDisplayNodeRespectThreadAffinityOfNode(self, ^{
|
||||||
[self setNeedsDisplay];
|
[self setNeedsDisplay];
|
||||||
});
|
});
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
|
- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
|
||||||
{
|
{
|
||||||
|
|||||||
@ -58,6 +58,7 @@ struct ASTextKitAttributes {
|
|||||||
NSLineBreakMode lineBreakMode;
|
NSLineBreakMode lineBreakMode;
|
||||||
/**
|
/**
|
||||||
The maximum number of lines to draw in the drawable region. Leave blank or set to 0 to define no maximum.
|
The maximum number of lines to draw in the drawable region. Leave blank or set to 0 to define no maximum.
|
||||||
|
This is required to apply scale factors to shrink text to fit within a number of lines
|
||||||
*/
|
*/
|
||||||
NSUInteger maximumNumberOfLines;
|
NSUInteger maximumNumberOfLines;
|
||||||
/**
|
/**
|
||||||
@ -82,9 +83,13 @@ struct ASTextKitAttributes {
|
|||||||
*/
|
*/
|
||||||
CGFloat shadowRadius;
|
CGFloat shadowRadius;
|
||||||
/**
|
/**
|
||||||
The minimum scale that the textnode can apply to fit long words in constrained size.
|
An array of scale factors in descending order to apply to the text to try to make it fit into a constrained size.
|
||||||
*/
|
*/
|
||||||
CGFloat minimumScaleFactor;
|
NSArray *pointSizeScaleFactors;
|
||||||
|
/**
|
||||||
|
The currently applied scale factor. Only valid if pointSizeScaleFactors are provided. Defaults to 0 (no scaling)
|
||||||
|
*/
|
||||||
|
CGFloat currentScaleFactor;
|
||||||
/**
|
/**
|
||||||
A pointer to a function that that returns a custom layout manager subclass. If nil, defaults to NSLayoutManager.
|
A pointer to a function that that returns a custom layout manager subclass. If nil, defaults to NSLayoutManager.
|
||||||
*/
|
*/
|
||||||
@ -112,8 +117,10 @@ struct ASTextKitAttributes {
|
|||||||
[shadowColor copy],
|
[shadowColor copy],
|
||||||
shadowOpacity,
|
shadowOpacity,
|
||||||
shadowRadius,
|
shadowRadius,
|
||||||
minimumScaleFactor,
|
pointSizeScaleFactors,
|
||||||
layoutManagerFactory
|
currentScaleFactor,
|
||||||
|
layoutManagerFactory,
|
||||||
|
layoutManagerDelegate,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -124,7 +131,8 @@ struct ASTextKitAttributes {
|
|||||||
&& maximumNumberOfLines == other.maximumNumberOfLines
|
&& maximumNumberOfLines == other.maximumNumberOfLines
|
||||||
&& shadowOpacity == other.shadowOpacity
|
&& shadowOpacity == other.shadowOpacity
|
||||||
&& shadowRadius == other.shadowRadius
|
&& shadowRadius == other.shadowRadius
|
||||||
&& minimumScaleFactor == other.minimumScaleFactor
|
&& [pointSizeScaleFactors isEqualToArray:other.pointSizeScaleFactors]
|
||||||
|
&& currentScaleFactor == currentScaleFactor
|
||||||
&& layoutManagerFactory == other.layoutManagerFactory
|
&& layoutManagerFactory == other.layoutManagerFactory
|
||||||
&& CGSizeEqualToSize(shadowOffset, other.shadowOffset)
|
&& CGSizeEqualToSize(shadowOffset, other.shadowOffset)
|
||||||
&& _objectsEqual(exclusionPaths, other.exclusionPaths)
|
&& _objectsEqual(exclusionPaths, other.exclusionPaths)
|
||||||
|
|||||||
@ -1,20 +1,46 @@
|
|||||||
//
|
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||||
// ASTextKitFontSizeAdjuster.h
|
* All rights reserved.
|
||||||
// AsyncDisplayKit
|
*
|
||||||
//
|
* This source code is licensed under the BSD-style license found in the
|
||||||
// Created by Luke on 1/20/16.
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
// Copyright © 2016 Facebook. All rights reserved.
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
//
|
*/
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "ASTextKitAttributes.h"
|
||||||
|
#import "ASTextKitContext.h"
|
||||||
|
|
||||||
@interface ASTextKitFontSizeAdjuster : NSObject
|
@interface ASTextKitFontSizeAdjuster : NSObject
|
||||||
|
|
||||||
@property (nonatomic, assign) CGSize constrainedSize;
|
@property (nonatomic, assign) CGSize constrainedSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a class that will return a scale factor the will make a string fit inside the constrained size.
|
||||||
|
*
|
||||||
|
* "Fitting" means that both the longest word in the string will fit without breaking in the constrained
|
||||||
|
* size's width AND that the entire string will try to fit within attribute's maximumLineCount. The amount
|
||||||
|
* that the string will scale is based upon the attribute's pointSizeScaleFactors. If the string cannot fit
|
||||||
|
* in the given width/number of lines, the smallest scale factor will be returned.
|
||||||
|
*
|
||||||
|
* @param context The text kit context
|
||||||
|
* @param constrainedSize The constrained size to render into
|
||||||
|
* @param textComponentAttributes The renderer's text attributes
|
||||||
|
*/
|
||||||
- (instancetype)initWithContext:(ASTextKitContext *)context
|
- (instancetype)initWithContext:(ASTextKitContext *)context
|
||||||
minimumScaleFactor:(CGFloat)minimumScaleFactor
|
constrainedSize:(CGSize)constrainedSize
|
||||||
constrainedSize:(CGSize)constrainedSize;
|
textKitAttributes:(const ASTextKitAttributes &)textComponentAttributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the best fit scale factor for the text
|
||||||
|
*/
|
||||||
|
- (CGFloat)scaleFactor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes all of the attributed string attributes dealing with size (font size, line spacing, kerning, etc) and
|
||||||
|
* scales them by the scaleFactor. I wouldn't be surprised if I missed some in here.
|
||||||
|
*/
|
||||||
|
+ (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor;
|
||||||
|
|
||||||
- (void) adjustFontSize;
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,98 +0,0 @@
|
|||||||
//
|
|
||||||
// ASTextKitFontSizeAdjuster.m
|
|
||||||
// AsyncDisplayKit
|
|
||||||
//
|
|
||||||
// Created by Luke on 1/20/16.
|
|
||||||
// Copyright © 2016 Facebook. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import "ASTextKitContext.h"
|
|
||||||
#import "ASTextKitFontSizeAdjuster.h"
|
|
||||||
|
|
||||||
@implementation ASTextKitFontSizeAdjuster
|
|
||||||
{
|
|
||||||
__weak ASTextKitContext *_context;
|
|
||||||
CGFloat _minimumScaleFactor;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)initWithContext:(ASTextKitContext *)context
|
|
||||||
minimumScaleFactor:(CGFloat)minimumScaleFactor
|
|
||||||
constrainedSize:(CGSize)constrainedSize
|
|
||||||
{
|
|
||||||
if (self = [super init]) {
|
|
||||||
_context = context;
|
|
||||||
_minimumScaleFactor = minimumScaleFactor;
|
|
||||||
_constrainedSize = constrainedSize;
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (CGSize)sizeForAttributedString:(NSAttributedString *)attrString
|
|
||||||
{
|
|
||||||
return [attrString boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)
|
|
||||||
options:NSStringDrawingUsesLineFragmentOrigin
|
|
||||||
context:nil].size;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
- (void) adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor
|
|
||||||
{
|
|
||||||
{
|
|
||||||
[attrString beginEditing];
|
|
||||||
|
|
||||||
[attrString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, attrString.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) {
|
|
||||||
|
|
||||||
UIFont* font = value;
|
|
||||||
font = [font fontWithSize:font.pointSize * scaleFactor];
|
|
||||||
|
|
||||||
[attrString removeAttribute:NSFontAttributeName range:range];
|
|
||||||
[attrString addAttribute:NSFontAttributeName value:font range:range];
|
|
||||||
}];
|
|
||||||
|
|
||||||
[attrString endEditing];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
- (void)adjustFontSize
|
|
||||||
{
|
|
||||||
if (_minimumScaleFactor <= 0 || _minimumScaleFactor >= 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
|
||||||
NSString *str = textStorage.string;
|
|
||||||
NSArray *words = [str componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
|
||||||
NSString *longestWordNeedingResize = @"";
|
|
||||||
for (NSString *word in words) {
|
|
||||||
if ([word length] > [longestWordNeedingResize length]) {
|
|
||||||
longestWordNeedingResize = word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([longestWordNeedingResize length] == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSRange range = [str rangeOfString:longestWordNeedingResize];
|
|
||||||
NSMutableAttributedString *attrString = [textStorage attributedSubstringFromRange:range].mutableCopy;
|
|
||||||
CGSize defaultSize = [self sizeForAttributedString:attrString];
|
|
||||||
|
|
||||||
if (defaultSize.width > _constrainedSize.width) {
|
|
||||||
[attrString removeAttribute:NSParagraphStyleAttributeName range:NSMakeRange(0, [attrString length])];
|
|
||||||
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
|
|
||||||
context.minimumScaleFactor = _minimumScaleFactor;
|
|
||||||
[attrString boundingRectWithSize:CGSizeMake(_constrainedSize.width, defaultSize.height)
|
|
||||||
options:NSStringDrawingUsesLineFragmentOrigin
|
|
||||||
context:context];
|
|
||||||
|
|
||||||
[self adjustFontSizeForAttributeString:attrString withScaleFactor:context.actualScaleFactor];
|
|
||||||
|
|
||||||
if ([self sizeForAttributedString:attrString].width <= _constrainedSize.width) {
|
|
||||||
[self adjustFontSizeForAttributeString:textStorage withScaleFactor:context.actualScaleFactor];
|
|
||||||
NSLog(@"ASTextKitFontSizeAdjuster : adjusted \"%@\"to fontsize actualScaleFactor:%f", longestWordNeedingResize, context.actualScaleFactor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
185
AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm
Normal file
185
AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/* 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 "ASTextKitContext.h"
|
||||||
|
#import "ASTextKitFontSizeAdjuster.h"
|
||||||
|
#import "ASLayoutManager.h"
|
||||||
|
|
||||||
|
#import <mutex>
|
||||||
|
|
||||||
|
//#define LOG(...) NSLog(__VA_ARGS__)
|
||||||
|
#define LOG(...)
|
||||||
|
|
||||||
|
@implementation ASTextKitFontSizeAdjuster
|
||||||
|
{
|
||||||
|
__weak ASTextKitContext *_context;
|
||||||
|
ASTextKitAttributes _attributes;
|
||||||
|
std::mutex _textKitMutex;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithContext:(ASTextKitContext *)context
|
||||||
|
constrainedSize:(CGSize)constrainedSize
|
||||||
|
textKitAttributes:(const ASTextKitAttributes &)textComponentAttributes;
|
||||||
|
{
|
||||||
|
if (self = [super init]) {
|
||||||
|
_context = context;
|
||||||
|
_constrainedSize = constrainedSize;
|
||||||
|
_attributes = textComponentAttributes;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor
|
||||||
|
{
|
||||||
|
[attrString beginEditing];
|
||||||
|
|
||||||
|
// scale all the attributes that will change the bounding box
|
||||||
|
[attrString enumerateAttributesInRange:NSMakeRange(0, attrString.length) options:0 usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
|
||||||
|
if (attrs[NSFontAttributeName] != nil) {
|
||||||
|
UIFont *font = attrs[NSFontAttributeName];
|
||||||
|
font = [font fontWithSize:roundf(font.pointSize * scaleFactor)];
|
||||||
|
[attrString removeAttribute:NSFontAttributeName range:range];
|
||||||
|
[attrString addAttribute:NSFontAttributeName value:font range:range];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrs[NSKernAttributeName] != nil) {
|
||||||
|
NSNumber *kerning = attrs[NSKernAttributeName];
|
||||||
|
[attrString removeAttribute:NSKernAttributeName range:range];
|
||||||
|
[attrString addAttribute:NSKernAttributeName value:@([kerning floatValue] * scaleFactor) range:range];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrs[NSParagraphStyleAttributeName] != nil) {
|
||||||
|
NSMutableParagraphStyle *paragraphStyle = [attrs[NSParagraphStyleAttributeName] mutableCopy];
|
||||||
|
paragraphStyle.lineSpacing = (paragraphStyle.lineSpacing * scaleFactor);
|
||||||
|
paragraphStyle.paragraphSpacing = (paragraphStyle.paragraphSpacing * scaleFactor);
|
||||||
|
paragraphStyle.firstLineHeadIndent = (paragraphStyle.firstLineHeadIndent * scaleFactor);
|
||||||
|
paragraphStyle.headIndent = (paragraphStyle.headIndent * scaleFactor);
|
||||||
|
paragraphStyle.tailIndent = (paragraphStyle.tailIndent * scaleFactor);
|
||||||
|
paragraphStyle.minimumLineHeight = (paragraphStyle.minimumLineHeight * scaleFactor);
|
||||||
|
paragraphStyle.maximumLineHeight = (paragraphStyle.maximumLineHeight * scaleFactor);
|
||||||
|
paragraphStyle.lineHeightMultiple = (paragraphStyle.lineHeightMultiple * scaleFactor);
|
||||||
|
paragraphStyle.paragraphSpacing = (paragraphStyle.paragraphSpacing * scaleFactor);
|
||||||
|
|
||||||
|
[attrString removeAttribute:NSParagraphStyleAttributeName range:range];
|
||||||
|
[attrString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range];
|
||||||
|
}
|
||||||
|
|
||||||
|
}];
|
||||||
|
|
||||||
|
[attrString endEditing];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSUInteger)lineCountForString:(NSAttributedString *)attributedString
|
||||||
|
{
|
||||||
|
NSUInteger lineCount = 0;
|
||||||
|
|
||||||
|
static std::mutex __static_mutex;
|
||||||
|
std::lock_guard<std::mutex> l(__static_mutex);
|
||||||
|
|
||||||
|
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
|
||||||
|
NSLayoutManager *layoutManager = _attributes.layoutManagerFactory ? _attributes.layoutManagerFactory() : [[ASLayoutManager alloc] init];
|
||||||
|
layoutManager.usesFontLeading = NO;
|
||||||
|
[textStorage addLayoutManager:layoutManager];
|
||||||
|
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:_constrainedSize];
|
||||||
|
|
||||||
|
textContainer.lineFragmentPadding = 0;
|
||||||
|
textContainer.lineBreakMode = _attributes.lineBreakMode;
|
||||||
|
|
||||||
|
// use 0 regardless of what is in the attributes so that we get an accurate line count
|
||||||
|
textContainer.maximumNumberOfLines = 0;
|
||||||
|
textContainer.exclusionPaths = _attributes.exclusionPaths;
|
||||||
|
[layoutManager addTextContainer:textContainer];
|
||||||
|
|
||||||
|
NSRange lineRange = { 0, 0 };
|
||||||
|
while (NSMaxRange(lineRange) < [layoutManager numberOfGlyphs]/* && lineCount <= _attributes.maximumNumberOfLines*/) {
|
||||||
|
[layoutManager lineFragmentRectForGlyphAtIndex:NSMaxRange(lineRange) effectiveRange:&lineRange];
|
||||||
|
lineCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lineCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CGFloat)scaleFactor
|
||||||
|
{
|
||||||
|
if ([_attributes.pointSizeScaleFactors count] == 0 || isinf(_constrainedSize.width)) {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
__block CGFloat adjustedScale = 1.0;
|
||||||
|
|
||||||
|
NSArray *scaleFactors = _attributes.pointSizeScaleFactors;
|
||||||
|
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||||
|
|
||||||
|
// Check for two different situations (and correct for both)
|
||||||
|
// 1. The longest word in the string fits without being wrapped
|
||||||
|
// 2. The entire text fits in the given constrained size.
|
||||||
|
|
||||||
|
NSString *str = textStorage.string;
|
||||||
|
NSArray *words = [str componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||||
|
NSString *longestWordNeedingResize = @"";
|
||||||
|
for (NSString *word in words) {
|
||||||
|
if ([word length] > [longestWordNeedingResize length]) {
|
||||||
|
longestWordNeedingResize = word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NSUInteger scaleIndex = 0;
|
||||||
|
|
||||||
|
// find the longest word and make sure it fits in the constrained width
|
||||||
|
if ([longestWordNeedingResize length] > 0) {
|
||||||
|
|
||||||
|
NSRange longestWordRange = [str rangeOfString:longestWordNeedingResize];
|
||||||
|
NSMutableAttributedString *attrString = [textStorage attributedSubstringFromRange:longestWordRange].mutableCopy;
|
||||||
|
CGSize longestWordSize = [attrString boundingRectWithSize:CGSizeMake(FLT_MAX, FLT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
|
||||||
|
|
||||||
|
// check if the longest word is larger than our constrained width
|
||||||
|
if (longestWordSize.width > _constrainedSize.width) {
|
||||||
|
|
||||||
|
// we have a word that is too long. Loop through our scale factors until we fit
|
||||||
|
for (NSNumber *scaleFactor in scaleFactors) {
|
||||||
|
// even if we still don't fit, save this scaleFactor so more of the word will fit
|
||||||
|
adjustedScale = [scaleFactor floatValue];
|
||||||
|
|
||||||
|
// adjust here so we start at the proper place in our scale array if we have too many lines
|
||||||
|
scaleIndex++;
|
||||||
|
|
||||||
|
if (ceilf(longestWordSize.width * [scaleFactor floatValue]) <= _constrainedSize.width) {
|
||||||
|
// we fit! we are done
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_attributes.maximumNumberOfLines > 0) {
|
||||||
|
// get the number of lines in our possibly scaled string
|
||||||
|
NSUInteger numberOfLines = [self lineCountForString:textStorage];
|
||||||
|
if (numberOfLines > _attributes.maximumNumberOfLines) {
|
||||||
|
|
||||||
|
for (NSUInteger index = scaleIndex; index < scaleFactors.count; index++) {
|
||||||
|
NSMutableAttributedString *entireAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage];
|
||||||
|
[[self class] adjustFontSizeForAttributeString:entireAttributedString withScaleFactor:[scaleFactors[index] floatValue]];
|
||||||
|
|
||||||
|
|
||||||
|
// save away this scale factor. Even if we don't fit completely we should still scale down
|
||||||
|
adjustedScale = [scaleFactors[index] floatValue];
|
||||||
|
|
||||||
|
if ([self lineCountForString:entireAttributedString] <= _attributes.maximumNumberOfLines) {
|
||||||
|
// we fit! we are done
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}];
|
||||||
|
return adjustedScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@ -55,6 +55,8 @@
|
|||||||
|
|
||||||
@property (nonatomic, assign, readwrite) CGSize constrainedSize;
|
@property (nonatomic, assign, readwrite) CGSize constrainedSize;
|
||||||
|
|
||||||
|
@property (nonatomic, assign, readonly) CGFloat currentScaleFactor;
|
||||||
|
|
||||||
#pragma mark - Drawing
|
#pragma mark - Drawing
|
||||||
/*
|
/*
|
||||||
Draw the renderer's text content into the bounds provided.
|
Draw the renderer's text content into the bounds provided.
|
||||||
|
|||||||
@ -49,6 +49,9 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
|
|||||||
_constrainedSize = constrainedSize;
|
_constrainedSize = constrainedSize;
|
||||||
_attributes = attributes;
|
_attributes = attributes;
|
||||||
_sizeIsCalculated = NO;
|
_sizeIsCalculated = NO;
|
||||||
|
if ([attributes.pointSizeScaleFactors count] > 0) {
|
||||||
|
_currentScaleFactor = attributes.currentScaleFactor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@ -84,8 +87,8 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
|
|||||||
// We must inset the constrained size by the size of the shadower.
|
// We must inset the constrained size by the size of the shadower.
|
||||||
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
|
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
|
||||||
_fontSizeAdjuster = [[ASTextKitFontSizeAdjuster alloc] initWithContext:[self context]
|
_fontSizeAdjuster = [[ASTextKitFontSizeAdjuster alloc] initWithContext:[self context]
|
||||||
minimumScaleFactor:attributes.minimumScaleFactor
|
constrainedSize:shadowConstrainedSize
|
||||||
constrainedSize:shadowConstrainedSize];
|
textKitAttributes:attributes];
|
||||||
}
|
}
|
||||||
return _fontSizeAdjuster;
|
return _fontSizeAdjuster;
|
||||||
}
|
}
|
||||||
@ -137,8 +140,8 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
|
|||||||
- (void)_calculateSize
|
- (void)_calculateSize
|
||||||
{
|
{
|
||||||
[self truncater];
|
[self truncater];
|
||||||
if (_attributes.minimumScaleFactor < 1 && _attributes.minimumScaleFactor > 0) {
|
if ([_attributes.pointSizeScaleFactors count] > 0) {
|
||||||
[[self fontSizeAdjuster] adjustFontSize];
|
_currentScaleFactor = [[self fontSizeAdjuster] scaleFactor];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by
|
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by
|
||||||
@ -156,8 +159,12 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
|
|||||||
// TextKit often returns incorrect glyph bounding rects in the horizontal direction, so we clip to our bounding rect
|
// TextKit often returns incorrect glyph bounding rects in the horizontal direction, so we clip to our bounding rect
|
||||||
// to make sure our width calculations aren't being offset by glyphs going beyond the constrained rect.
|
// to make sure our width calculations aren't being offset by glyphs going beyond the constrained rect.
|
||||||
boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size});
|
boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size});
|
||||||
|
CGSize boundingSize = [_shadower outsetSizeWithInsetSize:boundingRect.size];
|
||||||
_calculatedSize = [_shadower outsetSizeWithInsetSize:boundingRect.size];
|
_calculatedSize = CGSizeMake(boundingSize.width, boundingSize.height);
|
||||||
|
|
||||||
|
if (_currentScaleFactor > 0.0 && _currentScaleFactor < 1.0) {
|
||||||
|
_calculatedSize.height = ceilf(_calculatedSize.height * _currentScaleFactor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Drawing
|
#pragma mark - Drawing
|
||||||
@ -176,11 +183,32 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
|
|||||||
LOG(@"%@, shadowInsetBounds = %@",self, NSStringFromCGRect(shadowInsetBounds));
|
LOG(@"%@, shadowInsetBounds = %@",self, NSStringFromCGRect(shadowInsetBounds));
|
||||||
|
|
||||||
[[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
[[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
|
||||||
|
|
||||||
|
NSTextStorage *scaledTextStorage = nil;
|
||||||
|
BOOL isScaled = (self.currentScaleFactor > 0 && self.currentScaleFactor < 1.0);
|
||||||
|
|
||||||
|
if (isScaled) {
|
||||||
|
// if we are going to scale the text, swap out the non-scaled text for the scaled version.
|
||||||
|
NSMutableAttributedString *scaledString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage];
|
||||||
|
[ASTextKitFontSizeAdjuster adjustFontSizeForAttributeString:scaledString withScaleFactor:_currentScaleFactor];
|
||||||
|
scaledTextStorage = [[NSTextStorage alloc] initWithAttributedString:scaledString];
|
||||||
|
|
||||||
|
[textStorage removeLayoutManager:layoutManager];
|
||||||
|
[scaledTextStorage addLayoutManager:layoutManager];
|
||||||
|
}
|
||||||
|
|
||||||
LOG(@"usedRect: %@", NSStringFromCGRect([layoutManager usedRectForTextContainer:textContainer]));
|
LOG(@"usedRect: %@", NSStringFromCGRect([layoutManager usedRectForTextContainer:textContainer]));
|
||||||
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
|
NSRange glyphRange = [layoutManager glyphRangeForBoundingRect:CGRectMake(0,0,textContainer.size.width, textContainer.size.height) inTextContainer:textContainer];
|
||||||
LOG(@"boundingRect: %@", NSStringFromCGRect([layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer]));
|
LOG(@"boundingRect: %@", NSStringFromCGRect([layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer]));
|
||||||
|
|
||||||
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
|
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
|
||||||
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
|
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
|
||||||
|
|
||||||
|
if (isScaled) {
|
||||||
|
// put the non-scaled version back
|
||||||
|
[scaledTextStorage removeLayoutManager:layoutManager];
|
||||||
|
[textStorage addLayoutManager:layoutManager];
|
||||||
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
UIGraphicsPopContext();
|
UIGraphicsPopContext();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user