-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathTLDrawnText.m
192 lines (156 loc) · 6.62 KB
/
TLDrawnText.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//
// TLDrawnText.m
// TLCommon
//
// Created by Joshua Bleecher Snyder on 10/14/09.
//
#import "TLDrawnText.h"
#import "CGGeometry_TLCommon.h"
#import "CGContext_TLCommon.h"
#import "NSString_TLCommon.h"
#pragma mark -
@interface TLDrawnText ()
@end
#pragma mark -
@implementation TLDrawnText
@synthesize text;
@synthesize font;
@synthesize lineBreakMode;
@synthesize textAlignment;
@synthesize textColor;
@synthesize minimumTopPadding;
@synthesize flexibleTopPadding;
@synthesize minimumBottomPadding;
@synthesize flexibleBottomPadding;
@synthesize leftInset;
@synthesize rightInset;
@synthesize renderRect;
+ (TLDrawnText *)drawnText {
return [[[self alloc] init] autorelease];
}
- (id)init {
if(self = [super init]) {
}
return self;
}
- (CGSize)sizeWithWidth:(CGFloat)totalWidthIncludingSpaceForInsets {
CGFloat widthAfterRemovingInsets = totalWidthIncludingSpaceForInsets - self.leftInset - self.rightInset;
CGSize textRenderSize = [self.text sizeWithFont:self.font
constrainedToSize:CGSizeMake(widthAfterRemovingInsets, CGFLOAT_MAX)
lineBreakMode:self.lineBreakMode];
return CGSizeByAddingWidth(textRenderSize, self.leftInset + self.rightInset);
}
- (CGRect)insetAdjustedRenderRect {
CGRect textRenderRect = CGRectMake(self.renderRect.origin.x + self.leftInset,
self.renderRect.origin.y,
self.renderRect.size.width - self.leftInset - self.rightInset,
self.renderRect.size.height);
return textRenderRect;
}
- (CGRect)render {
CGRect textRenderRect = [self insetAdjustedRenderRect];
[self.textColor setFill];
CGSize renderedTextSize = [self.text drawInRect:textRenderRect
withFont:self.font
lineBreakMode:self.lineBreakMode
alignment:self.textAlignment];
CGRect renderedTextRect = CGRectZero;
renderedTextRect.size = renderedTextSize;
renderedTextRect.origin.y = textRenderRect.origin.y;
switch(self.textAlignment) {
case UITextAlignmentLeft:
renderedTextRect.origin.x = textRenderRect.origin.x;
break;
case UITextAlignmentCenter:
renderedTextRect.origin.x = textRenderRect.origin.x + OffsetToCenterFloatInFloat(renderedTextRect.size.width, textRenderRect.size.width);
break;
case UITextAlignmentRight:
renderedTextRect.origin.x = CGRectGetMaxX(textRenderRect) - renderedTextSize.width;
break;
}
return renderedTextRect;
}
+ (CGFloat)layoutInRect:(CGRect)containingRect canOverflowBottomOfRect:(BOOL)rectCanBeMadeBigger texts:(TLDrawnText *)firstText, ... {
va_list texts;
NSUInteger numberOfFlexibleSections = 0;
CGFloat minimumHeight = 0;
// first pass -- gather layout information
va_start(texts, firstText);
BOOL previousTextHadFlexibleBottomPadding = NO;
for(TLDrawnText *text = firstText; text != nil; text = va_arg(texts, TLDrawnText *)) {
// count all flexible bottom paddings, but only count flexible top padding if the previous one didn't
// have flexible bottom padding, since flexible top + flexible bottom size by side make just one
// flexible section
if(text.flexibleBottomPadding) {
numberOfFlexibleSections++;
}
if(!previousTextHadFlexibleBottomPadding && text.flexibleTopPadding) {
numberOfFlexibleSections++;
}
previousTextHadFlexibleBottomPadding = text.flexibleBottomPadding;
// total up the minimum height required to render our text
// use the renderRect as temp storage for the render size so we don't have to recalculate later
// we're setting the renderRect anyway, so it is ok to use for our own purposes now
text.renderRect = CGRectZeroWithSize([text sizeWithWidth:containingRect.size.width]);
minimumHeight += text.minimumTopPadding + text.minimumBottomPadding + text.renderRect.size.height;
}
va_end(texts);
// precalculate key layout variables
CGFloat flexibleSectionHeight = 0.0f; // how tall is each flexible section?
CGFloat scalingFactor = 1.0f; // if we are too tall but the containing rect is fixed in size, how much does each piece need to be scaled down?
BOOL scalingDown = NO;
if(minimumHeight <= containingRect.size.height) {
// we're ok; increase the flexible section height to take up the extra space
if(numberOfFlexibleSections > 0) {
flexibleSectionHeight = (containingRect.size.height - minimumHeight) / (float)numberOfFlexibleSections;
}
} else {
// too big
if(!rectCanBeMadeBigger) {
scalingDown = YES;
// got to scale everything down by this factor to fit
scalingFactor = containingRect.size.height / minimumHeight;
}
}
// second pass -- actually lay out texts in accordance with flexible size and scaling factor
va_start(texts, firstText);
previousTextHadFlexibleBottomPadding = NO;
CGFloat runningYOffset = 0;
for(TLDrawnText *text = firstText; text != nil; text = va_arg(texts, TLDrawnText *)) {
if(!previousTextHadFlexibleBottomPadding && text.flexibleTopPadding) {
// need to add in flexible padding up top
runningYOffset += scalingDown ? (text.minimumTopPadding * scalingFactor) : (flexibleSectionHeight + text.minimumTopPadding);
} else {
runningYOffset += text.minimumTopPadding * scalingFactor;
}
previousTextHadFlexibleBottomPadding = text.flexibleBottomPadding;
// grab the text height before flooring occurs, so
// flooring doesn't have a cumulative effect
CGFloat renderedTextHeight = text.renderRect.size.height;
// make sure renderRect is pixel-aligned for clearer text rendering;
// use floor to make sure we don't accidentally overflow
text.renderRect = CGRectFlooredToNearestPixel(CGRectMake(containingRect.origin.x,
runningYOffset,
containingRect.size.width,
text.renderRect.size.height * scalingFactor));
runningYOffset += renderedTextHeight;
if(text.flexibleBottomPadding) {
// need to add in flexible padding below
runningYOffset += scalingDown ? (text.minimumBottomPadding * scalingFactor) : (flexibleSectionHeight + text.minimumBottomPadding);
} else {
runningYOffset += text.minimumBottomPadding * scalingFactor;
}
}
va_end(texts);
return runningYOffset;
}
- (void)dealloc {
[text release];
text = nil;
[font release];
font = nil;
[textColor release];
textColor = nil;
[super dealloc];
}
@end