mirror of
https://github.com/danog/Telegram.git
synced 2024-12-02 09:27:55 +01:00
343 lines
14 KiB
Objective-C
343 lines
14 KiB
Objective-C
#import "TGSharedMediaFileThumbnailLabelView.h"
|
|
|
|
#import <CoreText/CoreText.h>
|
|
|
|
typedef enum {
|
|
TGSharedMediaFileThumbnailLabelViewLineEllipsisNone = 0,
|
|
TGSharedMediaFileThumbnailLabelViewLineEllipsisLeft,
|
|
TGSharedMediaFileThumbnailLabelViewLineEllipsisRight
|
|
} TGSharedMediaFileThumbnailLabelViewLineEllipsis;
|
|
|
|
@interface TGSharedMediaFileThumbnailLabelViewLine : NSObject
|
|
|
|
@property (nonatomic, readonly) CTLineRef line;
|
|
@property (nonatomic) CGSize size;
|
|
@property (nonatomic) CGFloat intrinsicWidth;
|
|
@property (nonatomic) TGSharedMediaFileThumbnailLabelViewLineEllipsis ellipsis;
|
|
|
|
@end
|
|
|
|
@implementation TGSharedMediaFileThumbnailLabelViewLine
|
|
|
|
- (instancetype)initWithLine:(CTLineRef)line size:(CGSize)size ellipsis:(TGSharedMediaFileThumbnailLabelViewLineEllipsis)ellipsis
|
|
{
|
|
self = [super init];
|
|
if (self != nil)
|
|
{
|
|
if (line != NULL)
|
|
_line = CFRetain(line);
|
|
_size = size;
|
|
_intrinsicWidth = size.width;
|
|
_ellipsis = ellipsis;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
if (_line != NULL)
|
|
CFRelease(_line);
|
|
}
|
|
|
|
@end
|
|
|
|
@interface TGSharedMediaFileThumbnailLabelView ()
|
|
{
|
|
NSAttributedString *_attributedString;
|
|
TGSharedMediaFileThumbnailLabelViewLine *_ellipsisLine;
|
|
NSArray *_lines;
|
|
bool _needsLayoutText;
|
|
CGFloat _lineHeight;
|
|
bool _displayBackground;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation TGSharedMediaFileThumbnailLabelView
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame
|
|
{
|
|
self = [super initWithFrame:frame];
|
|
if (self != nil)
|
|
{
|
|
_lineHeight = 15.0f;
|
|
self.opaque = false;
|
|
self.backgroundColor = [UIColor clearColor];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)setAttributedString:(NSAttributedString *)attributedString displayBackground:(bool)displayBackground
|
|
{
|
|
_attributedString = attributedString;
|
|
_needsLayoutText = true;
|
|
_displayBackground = displayBackground;
|
|
[self setNeedsDisplay];
|
|
}
|
|
|
|
- (void)setFrame:(CGRect)frame
|
|
{
|
|
if (!CGSizeEqualToSize(self.frame.size, frame.size))
|
|
{
|
|
_needsLayoutText = true;
|
|
[self setNeedsDisplay];
|
|
}
|
|
|
|
[super setFrame:frame];
|
|
}
|
|
|
|
- (void)_layoutText
|
|
{
|
|
_needsLayoutText = false;
|
|
if (_attributedString == nil)
|
|
return;
|
|
|
|
|
|
{
|
|
NSDictionary *attributes = _attributedString.length == 0 ? @{} : [_attributedString attributesAtIndex:0 effectiveRange:NULL];
|
|
|
|
static NSString *tokenString = nil;
|
|
if (tokenString == nil)
|
|
{
|
|
unichar tokenChar = 0x2026;
|
|
tokenString = [[NSString alloc] initWithCharacters:&tokenChar length:1];
|
|
}
|
|
|
|
NSAttributedString *truncationTokenString = [[NSAttributedString alloc] initWithString:tokenString attributes:attributes];
|
|
CTLineRef truncationToken = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)truncationTokenString);
|
|
CGFloat lineWidth = (float)CTLineGetTypographicBounds(truncationToken, NULL, NULL, NULL) - (float)CTLineGetTrailingWhitespaceWidth(truncationToken);
|
|
|
|
_ellipsisLine = [[TGSharedMediaFileThumbnailLabelViewLine alloc] initWithLine:truncationToken size:CGSizeMake(lineWidth, _lineHeight) ellipsis:TGSharedMediaFileThumbnailLabelViewLineEllipsisNone];
|
|
|
|
CFRelease(truncationToken);
|
|
}
|
|
|
|
CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((__bridge CFAttributedStringRef)_attributedString);
|
|
|
|
CFIndex preventClusteredBreakIndex = INT_MAX;
|
|
//preventClusteredBreakIndex = _attributedString.length - [_attributedString.string pathExtension].length;
|
|
|
|
NSMutableArray *lines = [[NSMutableArray alloc] init];
|
|
CFIndex lastIndex = 0;
|
|
CGFloat maxWidth = 64.0f;
|
|
CFIndex firstLineIndex = 0;
|
|
while (true)
|
|
{
|
|
CFIndex lineCharacterCount = CTTypesetterSuggestClusterBreak(typesetter, lastIndex, maxWidth);
|
|
if (lastIndex + lineCharacterCount > preventClusteredBreakIndex)
|
|
{
|
|
lineCharacterCount = preventClusteredBreakIndex - lastIndex;
|
|
preventClusteredBreakIndex = INT_MAX;
|
|
}
|
|
|
|
if (lineCharacterCount == 0)
|
|
break;
|
|
if (firstLineIndex == 0)
|
|
firstLineIndex = lineCharacterCount;
|
|
CTLineRef line = CTTypesetterCreateLineWithOffset(typesetter, CFRangeMake(lastIndex, lineCharacterCount), 100.0);
|
|
|
|
CGFloat lineWidth = (float)CTLineGetTypographicBounds(line, NULL, NULL, NULL) - (float)CTLineGetTrailingWhitespaceWidth(line);
|
|
|
|
[lines addObject:[[TGSharedMediaFileThumbnailLabelViewLine alloc] initWithLine:line size:CGSizeMake(lineWidth, _lineHeight) ellipsis:TGSharedMediaFileThumbnailLabelViewLineEllipsisNone]];
|
|
|
|
lastIndex += lineCharacterCount;
|
|
CFRelease(line);
|
|
}
|
|
|
|
if (lines.count >= 2)
|
|
{
|
|
if (lines.count > 2)
|
|
{
|
|
TGSharedMediaFileThumbnailLabelViewLine *lastLine = nil;
|
|
|
|
CFIndex index = _attributedString.length - 1;
|
|
while (index > firstLineIndex)
|
|
{
|
|
CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(index, _attributedString.length - index));
|
|
CGFloat lineWidth = (float)CTLineGetTypographicBounds(line, NULL, NULL, NULL) - (float)CTLineGetTrailingWhitespaceWidth(line);
|
|
if (lineWidth > maxWidth)
|
|
break;
|
|
|
|
lastLine = [[TGSharedMediaFileThumbnailLabelViewLine alloc] initWithLine:line size:CGSizeMake(lineWidth + _ellipsisLine.size.width + 1.0f, _lineHeight) ellipsis:TGSharedMediaFileThumbnailLabelViewLineEllipsisLeft];
|
|
|
|
CFRelease(line);
|
|
index--;
|
|
}
|
|
|
|
((TGSharedMediaFileThumbnailLabelViewLine *)lines[0]).ellipsis = TGSharedMediaFileThumbnailLabelViewLineEllipsisRight;
|
|
((TGSharedMediaFileThumbnailLabelViewLine *)lines[0]).size = (CGSize){((TGSharedMediaFileThumbnailLabelViewLine *)lines[0]).size.width + _ellipsisLine.size.width - 1.0f, _lineHeight};
|
|
lines = [[NSMutableArray alloc] initWithArray:@[lines.firstObject, lastLine != nil ? lastLine : lines.lastObject]];
|
|
}
|
|
|
|
CGFloat radius = 4.0f;
|
|
for (NSUInteger i = 0; i < lines.count - 1; i++)
|
|
{
|
|
TGSharedMediaFileThumbnailLabelViewLine *line = lines[i];
|
|
TGSharedMediaFileThumbnailLabelViewLine *nextLine = lines[i + 1];
|
|
if (ABS(line.size.width - nextLine.size.width) < radius * 2.0f)
|
|
{
|
|
if (line.size.width > nextLine.size.width)
|
|
line.size = (CGSize){nextLine.size.width + radius * 2.0f, line.size.height};
|
|
else
|
|
nextLine.size = (CGSize){line.size.width + radius * 2.0f, nextLine.size.height};
|
|
}
|
|
}
|
|
}
|
|
|
|
CFRelease(typesetter);
|
|
|
|
_lines = lines;
|
|
}
|
|
|
|
- (void)sizeToFit
|
|
{
|
|
if (_needsLayoutText)
|
|
[self _layoutText];
|
|
|
|
CGSize size = CGSizeZero;
|
|
for (TGSharedMediaFileThumbnailLabelViewLine *line in _lines)
|
|
{
|
|
size.width = MAX(size.width, line.size.width);
|
|
size.height += line.size.height;
|
|
}
|
|
self.frame = (CGRect){self.frame.origin, {size.width + 8.0f, size.height + 2.0f}};
|
|
}
|
|
|
|
- (void)drawRect:(CGRect)__unused rect
|
|
{
|
|
if (_needsLayoutText)
|
|
[self _layoutText];
|
|
|
|
CGContextRef context = UIGraphicsGetCurrentContext();
|
|
CGContextSaveGState(context);
|
|
|
|
CGContextTranslateCTM(context, self.frame.size.width / 2.0f, self.frame.size.height / 2.0f);
|
|
CGContextScaleCTM(context, 1.0f, -1.0f);
|
|
CGContextTranslateCTM(context, -self.frame.size.width / 2.0f, -self.frame.size.height / 2.0f);
|
|
|
|
if (_displayBackground)
|
|
{
|
|
CGContextSetFillColorWithColor(context, [UIColor colorWithWhite:0.0f alpha:0.3f].CGColor);
|
|
CGContextBeginPath(context);
|
|
|
|
CGFloat radius = 2.0f;
|
|
|
|
CGFloat lineOffset = 0.0f;
|
|
CGRect lastLineFrame = CGRectZero;
|
|
|
|
NSUInteger index = 0;
|
|
for (TGSharedMediaFileThumbnailLabelViewLine *line in _lines.reverseObjectEnumerator)
|
|
{
|
|
CGRect lineFrame = (CGRect){CGPointMake(CGFloor((self.frame.size.width - (line.size.width + 4.0f)) / 2.0f), 0.0f + lineOffset), {line.size.width + 4.0f, line.size.height}};
|
|
|
|
if (index == 0)
|
|
CGContextMoveToPoint(context, CGRectGetMidX(lineFrame), CGRectGetMinY(lineFrame));
|
|
|
|
if (index != 0)
|
|
{
|
|
if (lastLineFrame.size.width < lineFrame.size.width)
|
|
{
|
|
CGContextAddArcToPoint(context, CGRectGetMaxX(lastLineFrame), CGRectGetMaxY(lastLineFrame), CGRectGetMaxX(lastLineFrame) + radius, CGRectGetMaxY(lastLineFrame), radius);
|
|
CGContextAddLineToPoint(context, CGRectGetMaxX(lineFrame) - radius, CGRectGetMinY(lineFrame));
|
|
}
|
|
else
|
|
{
|
|
CGContextAddArcToPoint(context, CGRectGetMaxX(lastLineFrame), CGRectGetMaxY(lastLineFrame), CGRectGetMaxX(lastLineFrame) - radius, CGRectGetMaxY(lastLineFrame), radius);
|
|
CGContextAddLineToPoint(context, CGRectGetMaxX(lineFrame) + radius, CGRectGetMinY(lineFrame));
|
|
}
|
|
|
|
CGContextAddArcToPoint(context, CGRectGetMaxX(lineFrame), CGRectGetMinY(lineFrame), CGRectGetMaxX(lineFrame), CGRectGetMinY(lineFrame) + radius, radius);
|
|
CGContextAddLineToPoint(context, CGRectGetMaxX(lineFrame), CGRectGetMaxY(lineFrame) - radius);
|
|
}
|
|
else
|
|
{
|
|
CGContextAddLineToPoint(context, CGRectGetMaxX(lineFrame) - radius, CGRectGetMinY(lineFrame));
|
|
CGContextAddArcToPoint(context, CGRectGetMaxX(lineFrame), CGRectGetMinY(lineFrame), CGRectGetMaxX(lineFrame), CGRectGetMinY(lineFrame) + radius, radius);
|
|
CGContextAddLineToPoint(context, CGRectGetMaxX(lineFrame), CGRectGetMaxY(lineFrame) - radius);
|
|
}
|
|
|
|
if (index == _lines.count - 1)
|
|
{
|
|
CGContextAddArcToPoint(context, CGRectGetMaxX(lineFrame), CGRectGetMaxY(lineFrame), CGRectGetMaxX(lineFrame) - radius, CGRectGetMaxY(lineFrame), radius);
|
|
CGContextAddLineToPoint(context, CGRectGetMidX(lineFrame), CGRectGetMaxY(lineFrame));
|
|
}
|
|
|
|
lineOffset += _lineHeight;
|
|
lastLineFrame = lineFrame;
|
|
|
|
index++;
|
|
}
|
|
|
|
lineOffset -= _lineHeight;
|
|
lastLineFrame = CGRectZero;
|
|
index = _lines.count - 1;
|
|
for (TGSharedMediaFileThumbnailLabelViewLine *line in _lines)
|
|
{
|
|
CGRect lineFrame = (CGRect){CGPointMake(CGFloor((self.frame.size.width - (line.size.width + 4.0f)) / 2.0f), 0.0f + lineOffset), {line.size.width + 4.0f, line.size.height}};
|
|
|
|
if (index == _lines.count - 1)
|
|
{
|
|
CGContextAddLineToPoint(context, CGRectGetMinX(lineFrame) + radius, CGRectGetMaxY(lineFrame));
|
|
CGContextAddArcToPoint(context, CGRectGetMinX(lineFrame), CGRectGetMaxY(lineFrame), CGRectGetMinX(lineFrame), CGRectGetMaxY(lineFrame) - radius, radius);
|
|
CGContextAddLineToPoint(context, CGRectGetMinX(lineFrame), CGRectGetMinY(lineFrame) + radius);
|
|
}
|
|
else
|
|
{
|
|
if (lastLineFrame.size.width > lineFrame.size.width)
|
|
{
|
|
CGContextAddArcToPoint(context, CGRectGetMinX(lastLineFrame), CGRectGetMinY(lastLineFrame), CGRectGetMinX(lastLineFrame) + radius, CGRectGetMinY(lastLineFrame), radius);
|
|
CGContextAddLineToPoint(context, CGRectGetMinX(lineFrame) - radius, CGRectGetMaxY(lineFrame));
|
|
}
|
|
else
|
|
{
|
|
CGContextAddArcToPoint(context, CGRectGetMinX(lastLineFrame), CGRectGetMinY(lastLineFrame), CGRectGetMinX(lastLineFrame) - radius, CGRectGetMinY(lastLineFrame), radius);
|
|
CGContextAddLineToPoint(context, CGRectGetMinX(lineFrame) + radius, CGRectGetMaxY(lineFrame));
|
|
}
|
|
CGContextAddArcToPoint(context, CGRectGetMinX(lineFrame), CGRectGetMaxY(lineFrame), CGRectGetMinX(lineFrame), CGRectGetMaxY(lineFrame) - radius, radius);
|
|
CGContextAddLineToPoint(context, CGRectGetMinX(lineFrame), CGRectGetMinY(lineFrame) + radius);
|
|
}
|
|
|
|
if (index == 0)
|
|
{
|
|
//CGContextAddLineToPoint(context, -100.0f, 100.0f);
|
|
CGContextAddArcToPoint(context, CGRectGetMinX(lineFrame), CGRectGetMinY(lineFrame), CGRectGetMinX(lineFrame) + radius, CGRectGetMinY(lineFrame), radius);
|
|
}
|
|
|
|
lineOffset -= _lineHeight;
|
|
|
|
index--;
|
|
lastLineFrame = lineFrame;
|
|
}
|
|
|
|
CGContextClosePath(context);
|
|
CGContextFillPath(context);
|
|
}
|
|
|
|
CGFloat lineOffset = 0.0f;
|
|
for (TGSharedMediaFileThumbnailLabelViewLine *line in _lines.reverseObjectEnumerator)
|
|
{
|
|
CGPoint lineOrigin = CGPointMake(CGFloor((self.frame.size.width - line.size.width) / 2.0f), 3.0f + lineOffset);
|
|
if (line.ellipsis == TGSharedMediaFileThumbnailLabelViewLineEllipsisLeft)
|
|
{
|
|
CGContextSetTextPosition(context, lineOrigin.x, lineOrigin.y);
|
|
CTLineDraw(_ellipsisLine.line, context);
|
|
|
|
lineOrigin.x += _ellipsisLine.size.width;
|
|
}
|
|
CGContextSetTextPosition(context, lineOrigin.x, lineOrigin.y);
|
|
CTLineDraw(line.line, context);
|
|
|
|
if (line.ellipsis == TGSharedMediaFileThumbnailLabelViewLineEllipsisRight)
|
|
{
|
|
CGContextSetTextPosition(context, lineOrigin.x + line.intrinsicWidth, lineOrigin.y);
|
|
CTLineDraw(_ellipsisLine.line, context);
|
|
}
|
|
|
|
lineOffset += _lineHeight;
|
|
}
|
|
|
|
CGContextRestoreGState(context);
|
|
}
|
|
|
|
@end
|