forked from Cocoanetics/DTCoreText
/
DTCoreTextParagraphStyle.m
314 lines (255 loc) · 9.52 KB
/
DTCoreTextParagraphStyle.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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
//
// DTCoreTextParagraphStyle.m
// CoreTextExtensions
//
// Created by Oliver Drobnik on 4/14/11.
// Copyright 2011 Drobnik.com. All rights reserved.
//
#import "DTCoreTextParagraphStyle.h"
static NSCache *_paragraphStyleCache;
// use smaller list indent on iPhone OS
#if TARGET_OS_IPHONE
#define SPECIAL_LIST_INDENT 27.0f
#else
#define SPECIAL_LIST_INDENT 36.0f
#endif
static dispatch_semaphore_t selfLock;
@implementation DTCoreTextParagraphStyle
{
CGFloat firstLineHeadIndent;
CGFloat defaultTabInterval;
CGFloat paragraphSpacingBefore;
CGFloat paragraphSpacing;
CGFloat headIndent;
CGFloat listIndent;
CGFloat lineHeightMultiple;
CGFloat minimumLineHeight;
CGFloat maximumLineHeight;
CTTextAlignment _alignment;
CTWritingDirection baseWritingDirection;
NSMutableArray *_tabStops;
}
+ (DTCoreTextParagraphStyle *)defaultParagraphStyle
{
return [[DTCoreTextParagraphStyle alloc] init];
}
+ (NSString *)niceKeyFromParagraghStyle:(CTParagraphStyleRef)ctParagraphStyle {
// this is naughty: CTParagraphStyle has a description
NSString *key = [(__bridge id)ctParagraphStyle description];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"0x[0123456789abcdef]{1,8}"
options:NSRegularExpressionCaseInsensitive
error:nil];
NSString *newKey = [regex stringByReplacingMatchesInString:key
options:0
range:NSMakeRange(0, [key length])
withTemplate:@""];
return newKey;
}
+ (DTCoreTextParagraphStyle *)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)ctParagraphStyle
{
DTCoreTextParagraphStyle *returnParagraphStyle = NULL;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
_paragraphStyleCache = [[NSCache alloc] init];
selfLock = dispatch_semaphore_create(1);
});
// synchronize class-wide
dispatch_semaphore_wait(selfLock, DISPATCH_TIME_FOREVER);
{
NSString *key = [self niceKeyFromParagraghStyle:ctParagraphStyle];
returnParagraphStyle = [_paragraphStyleCache objectForKey:key];
if (!returnParagraphStyle)
{
returnParagraphStyle = [[DTCoreTextParagraphStyle alloc] initWithCTParagraphStyle:ctParagraphStyle];
[_paragraphStyleCache setObject:returnParagraphStyle forKey:key];
}
}
dispatch_semaphore_signal(selfLock);
return returnParagraphStyle;
}
- (id)init
{
if ((self = [super init]))
{
// defaults
firstLineHeadIndent = 0.0;
defaultTabInterval = 36.0;
baseWritingDirection = kCTWritingDirectionNatural;
_alignment = kCTNaturalTextAlignment;
lineHeightMultiple = 0.0;
minimumLineHeight = 0.0;
maximumLineHeight = 0.0;
paragraphSpacing = 0.0;
listIndent = SPECIAL_LIST_INDENT;
}
return self;
}
- (id)initWithCTParagraphStyle:(CTParagraphStyleRef)ctParagraphStyle
{
if ((self = [super init]))
{
CTParagraphStyleGetValueForSpecifier(ctParagraphStyle, kCTParagraphStyleSpecifierAlignment,sizeof(_alignment), &_alignment);
CTParagraphStyleGetValueForSpecifier(ctParagraphStyle, kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(firstLineHeadIndent), &firstLineHeadIndent);
CTParagraphStyleGetValueForSpecifier(ctParagraphStyle, kCTParagraphStyleSpecifierDefaultTabInterval, sizeof(defaultTabInterval), &defaultTabInterval);
__unsafe_unretained NSArray *stops; // Could use a CFArray too, leave as a reminder how to do this in the future
if (CTParagraphStyleGetValueForSpecifier(ctParagraphStyle, kCTParagraphStyleSpecifierTabStops, sizeof(stops), &stops))
{
self.tabStops = stops;
}
CTParagraphStyleGetValueForSpecifier(ctParagraphStyle, kCTParagraphStyleSpecifierParagraphSpacing, sizeof(paragraphSpacing), ¶graphSpacing);
CTParagraphStyleGetValueForSpecifier(ctParagraphStyle, kCTParagraphStyleSpecifierParagraphSpacingBefore,sizeof(paragraphSpacingBefore), ¶graphSpacingBefore);
CTParagraphStyleGetValueForSpecifier(ctParagraphStyle, kCTParagraphStyleSpecifierHeadIndent, sizeof(headIndent), &headIndent);
CTParagraphStyleGetValueForSpecifier(ctParagraphStyle, kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(baseWritingDirection), &baseWritingDirection);
CTParagraphStyleGetValueForSpecifier(ctParagraphStyle, kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(lineHeightMultiple), &lineHeightMultiple);
CTParagraphStyleGetValueForSpecifier(ctParagraphStyle, kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(minimumLineHeight), &minimumLineHeight);
CTParagraphStyleGetValueForSpecifier(ctParagraphStyle, kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(maximumLineHeight), &maximumLineHeight);
if (lineHeightMultiple)
{
// paragraph space is pre-multiplied
if (paragraphSpacing)
{
paragraphSpacing /= lineHeightMultiple;
}
if (paragraphSpacingBefore)
{
paragraphSpacingBefore /= lineHeightMultiple;
}
}
}
return self;
}
- (CTParagraphStyleRef)createCTParagraphStyle
{
// need to multiple paragraph spacing with line height multiplier
float tmpParagraphSpacing = paragraphSpacing;
float tmpParagraphSpacingBefore = paragraphSpacingBefore;
if (lineHeightMultiple&&(lineHeightMultiple!=1.0))
{
tmpParagraphSpacing *= lineHeightMultiple;
tmpParagraphSpacingBefore *= lineHeightMultiple;
}
// This just makes it that much easier to track down memory issues with tabstops
CFArrayRef stops = _tabStops ? CFArrayCreateCopy (NULL, (__bridge CFArrayRef)_tabStops) : NULL;
CTParagraphStyleSetting settings[] =
{
{kCTParagraphStyleSpecifierAlignment, sizeof(_alignment), &_alignment},
{kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(firstLineHeadIndent), &firstLineHeadIndent},
{kCTParagraphStyleSpecifierDefaultTabInterval, sizeof(defaultTabInterval), &defaultTabInterval},
{kCTParagraphStyleSpecifierTabStops, sizeof(stops), &stops},
{kCTParagraphStyleSpecifierParagraphSpacing, sizeof(tmpParagraphSpacing), &tmpParagraphSpacing},
{kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(tmpParagraphSpacingBefore), &tmpParagraphSpacingBefore},
{kCTParagraphStyleSpecifierHeadIndent, sizeof(headIndent), &headIndent},
{kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(baseWritingDirection), &baseWritingDirection},
{kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(lineHeightMultiple), &lineHeightMultiple},
{kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(minimumLineHeight), &minimumLineHeight},
{kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(maximumLineHeight), &maximumLineHeight}
};
CTParagraphStyleRef ret = CTParagraphStyleCreate(settings, 11);
if (stops) CFRelease(stops);
return ret;
}
- (void)addTabStopAtPosition:(CGFloat)position alignment:(CTTextAlignment)alignment
{
CTTextTabRef tab = CTTextTabCreate(alignment, position, NULL);
if(tab)
{
if (!_tabStops)
{
_tabStops = [[NSMutableArray alloc] init];
}
[_tabStops addObject:CFBridgingRelease(tab)];
//CFRelease(tab);
}
}
#pragma mark HTML Encoding
// representation of this paragraph style in css (as far as possible)
- (NSString *)cssStyleRepresentation
{
NSMutableString *retString = [NSMutableString string];
switch (_alignment)
{
case kCTLeftTextAlignment:
[retString appendString:@"text-align:left;"];
break;
case kCTRightTextAlignment:
[retString appendString:@"text-align:right;"];
break;
case kCTCenterTextAlignment:
[retString appendString:@"text-align:center;"];
break;
case kCTJustifiedTextAlignment:
[retString appendString:@"text-align:justify;"];
break;
case kCTNaturalTextAlignment:
// no output, this is default
break;
}
if (lineHeightMultiple && lineHeightMultiple!=1.0f)
{
[retString appendFormat:@"line-height:%.2fem;", lineHeightMultiple];
}
switch (baseWritingDirection)
{
case kCTWritingDirectionRightToLeft:
[retString appendString:@"direction:rtl;"];
break;
case kCTWritingDirectionLeftToRight:
[retString appendString:@"direction:ltr;"];
break;
case kCTWritingDirectionNatural:
// no output, this is default
break;
}
// return nil if no content
if ([retString length])
{
return retString;
}
else
{
return nil;
}
}
#pragma mark Copying
- (id)copyWithZone:(NSZone *)zone
{
DTCoreTextParagraphStyle *newObject = [[DTCoreTextParagraphStyle allocWithZone:zone] init];
newObject.firstLineHeadIndent = self.firstLineHeadIndent;
newObject.defaultTabInterval = self.defaultTabInterval;
newObject.paragraphSpacing = self.paragraphSpacing;
newObject.paragraphSpacingBefore = self.paragraphSpacingBefore;
newObject.lineHeightMultiple = self.lineHeightMultiple;
newObject.minimumLineHeight = self.minimumLineHeight;
newObject.maximumLineHeight = self.maximumLineHeight;
newObject.headIndent = self.headIndent;
newObject.listIndent = self.listIndent;
newObject.alignment = self.alignment;
newObject.baseWritingDirection = self.baseWritingDirection;
newObject.tabStops = self.tabStops; // copy
newObject.textLists = self.textLists; //copy
newObject.textBlocks = self.textBlocks; //copy
return newObject;
}
#pragma mark Properties
- (void)setTabStops:(NSArray *)tabStops
{
if (tabStops != _tabStops)
{
_tabStops = [tabStops mutableCopy]; // keep mutability
}
}
@synthesize firstLineHeadIndent;
@synthesize defaultTabInterval;
@synthesize paragraphSpacingBefore;
@synthesize paragraphSpacing;
@synthesize lineHeightMultiple;
@synthesize minimumLineHeight;
@synthesize maximumLineHeight;
@synthesize headIndent;
@synthesize listIndent;
@synthesize alignment = _alignment;
@synthesize textLists;
@synthesize textBlocks;
@synthesize baseWritingDirection;
@synthesize tabStops = _tabStops;
@end