summary refs log tree commit diff stats
path: root/libs/FontLabel/ZAttributedString.m
diff options
context:
space:
mode:
Diffstat (limited to 'libs/FontLabel/ZAttributedString.m')
-rwxr-xr-xlibs/FontLabel/ZAttributedString.m597
1 files changed, 597 insertions, 0 deletions
diff --git a/libs/FontLabel/ZAttributedString.m b/libs/FontLabel/ZAttributedString.m new file mode 100755 index 0000000..a4163bc --- /dev/null +++ b/libs/FontLabel/ZAttributedString.m
@@ -0,0 +1,597 @@
1//
2// ZAttributedString.m
3// FontLabel
4//
5// Created by Kevin Ballard on 9/22/09.
6// Copyright 2009 Zynga Game Networks. All rights reserved.
7//
8
9#import "ZAttributedString.h"
10#import "ZAttributedStringPrivate.h"
11
12@interface ZAttributedString ()
13- (NSUInteger)indexOfEffectiveAttributeRunForIndex:(NSUInteger)index;
14- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange uniquingOnName:(NSString *)attributeName;
15- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange
16 inRange:(NSRange)rangeLimit uniquingOnName:(NSString *)attributeName;
17@end
18
19@interface ZAttributedString ()
20@property (nonatomic, readonly) NSArray *attributes;
21@end
22
23@implementation ZAttributedString
24@synthesize string = _buffer;
25@synthesize attributes = _attributes;
26
27- (id)initWithAttributedString:(ZAttributedString *)attr {
28 NSParameterAssert(attr != nil);
29 if ((self = [super init])) {
30 _buffer = [attr->_buffer mutableCopy];
31 _attributes = [[NSMutableArray alloc] initWithArray:attr->_attributes copyItems:YES];
32 }
33 return self;
34}
35
36- (id)initWithString:(NSString *)str {
37 return [self initWithString:str attributes:nil];
38}
39
40- (id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes {
41 if ((self = [super init])) {
42 _buffer = [str mutableCopy];
43 _attributes = [[NSMutableArray alloc] initWithObjects:[ZAttributeRun attributeRunWithIndex:0 attributes:attributes], nil];
44 }
45 return self;
46}
47
48- (id)init {
49 return [self initWithString:@"" attributes:nil];
50}
51
52- (id)initWithCoder:(NSCoder *)decoder {
53 if ((self = [super init])) {
54 _buffer = [[decoder decodeObjectForKey:@"buffer"] mutableCopy];
55 _attributes = [[decoder decodeObjectForKey:@"attributes"] mutableCopy];
56 }
57 return self;
58}
59
60- (void)encodeWithCoder:(NSCoder *)aCoder {
61 [aCoder encodeObject:_buffer forKey:@"buffer"];
62 [aCoder encodeObject:_attributes forKey:@"attributes"];
63}
64
65- (id)copyWithZone:(NSZone *)zone {
66 return [self retain];
67}
68
69- (id)mutableCopyWithZone:(NSZone *)zone {
70 return [(ZMutableAttributedString *)[ZMutableAttributedString allocWithZone:zone] initWithAttributedString:self];
71}
72
73- (NSUInteger)length {
74 return [_buffer length];
75}
76
77- (NSString *)description {
78 NSMutableArray *components = [NSMutableArray arrayWithCapacity:[_attributes count]*2];
79 NSRange range = NSMakeRange(0, 0);
80 for (NSUInteger i = 0; i <= [_attributes count]; i++) {
81 range.location = NSMaxRange(range);
82 ZAttributeRun *run;
83 if (i < [_attributes count]) {
84 run = [_attributes objectAtIndex:i];
85 range.length = run.index - range.location;
86 } else {
87 run = nil;
88 range.length = [_buffer length] - range.location;
89 }
90 if (range.length > 0) {
91 [components addObject:[NSString stringWithFormat:@"\"%@\"", [_buffer substringWithRange:range]]];
92 }
93 if (run != nil) {
94 NSMutableArray *attrDesc = [NSMutableArray arrayWithCapacity:[run.attributes count]];
95 for (id key in run.attributes) {
96 [attrDesc addObject:[NSString stringWithFormat:@"%@: %@", key, [run.attributes objectForKey:key]]];
97 }
98 [components addObject:[NSString stringWithFormat:@"{%@}", [attrDesc componentsJoinedByString:@", "]]];
99 }
100 }
101 return [NSString stringWithFormat:@"%@", [components componentsJoinedByString:@" "]];
102}
103
104- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange {
105 NSParameterAssert(attributeName != nil);
106 return [[self attributesAtIndex:index effectiveRange:aRange uniquingOnName:attributeName] objectForKey:attributeName];
107}
108
109- (id)attribute:(NSString *)attributeName atIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit {
110 NSParameterAssert(attributeName != nil);
111 return [[self attributesAtIndex:index longestEffectiveRange:aRange inRange:rangeLimit uniquingOnName:attributeName] objectForKey:attributeName];
112}
113
114- (ZAttributedString *)attributedSubstringFromRange:(NSRange)aRange {
115 if (NSMaxRange(aRange) > [_buffer length]) {
116 @throw [NSException exceptionWithName:NSRangeException reason:@"range was outisde of the attributed string" userInfo:nil];
117 }
118 ZMutableAttributedString *newStr = [self mutableCopy];
119 if (aRange.location > 0) {
120 [newStr deleteCharactersInRange:NSMakeRange(0, aRange.location)];
121 }
122 if (NSMaxRange(aRange) < [_buffer length]) {
123 [newStr deleteCharactersInRange:NSMakeRange(aRange.length, [_buffer length] - NSMaxRange(aRange))];
124 }
125 return [newStr autorelease];
126}
127
128- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange {
129 return [NSDictionary dictionaryWithDictionary:[self attributesAtIndex:index effectiveRange:aRange uniquingOnName:nil]];
130}
131
132- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange inRange:(NSRange)rangeLimit {
133 return [NSDictionary dictionaryWithDictionary:[self attributesAtIndex:index longestEffectiveRange:aRange inRange:rangeLimit uniquingOnName:nil]];
134}
135
136#if Z_BLOCKS
137// Warning: this code has not been tested. The only guarantee is that it compiles.
138- (void)enumerateAttribute:(NSString *)attrName inRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
139 usingBlock:(void (^)(id, NSRange, BOOL*))block {
140 if (opts & ZAttributedStringEnumerationLongestEffectiveRangeNotRequired) {
141 [self enumerateAttributesInRange:enumerationRange options:opts usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
142 id value = [attrs objectForKey:attrName];
143 if (value != nil) {
144 block(value, range, stop);
145 }
146 }];
147 } else {
148 __block id oldValue = nil;
149 __block NSRange effectiveRange = NSMakeRange(0, 0);
150 [self enumerateAttributesInRange:enumerationRange options:opts usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
151 id value = [attrs objectForKey:attrName];
152 if (oldValue == nil) {
153 oldValue = value;
154 effectiveRange = range;
155 } else if (value != nil && [oldValue isEqual:value]) {
156 // combine the attributes
157 effectiveRange = NSUnionRange(effectiveRange, range);
158 } else {
159 BOOL innerStop = NO;
160 block(oldValue, effectiveRange, &innerStop);
161 if (innerStop) {
162 *stop = YES;
163 oldValue = nil;
164 } else {
165 oldValue = value;
166 }
167 }
168 }];
169 if (oldValue != nil) {
170 BOOL innerStop = NO; // necessary for the block, but unused
171 block(oldValue, effectiveRange, &innerStop);
172 }
173 }
174}
175
176- (void)enumerateAttributesInRange:(NSRange)enumerationRange options:(ZAttributedStringEnumerationOptions)opts
177 usingBlock:(void (^)(NSDictionary*, NSRange, BOOL*))block {
178 // copy the attributes so we can mutate the string if necessary during enumeration
179 // also clip the array during copy to only the subarray of attributes that cover the requested range
180 NSArray *attrs;
181 if (NSEqualRanges(enumerationRange, NSMakeRange(0, 0))) {
182 attrs = [NSArray arrayWithArray:_attributes];
183 } else {
184 // in this binary search, last is the first run after the range
185 NSUInteger first = 0, last = [_attributes count];
186 while (last > first+1) {
187 NSUInteger pivot = (last + first) / 2;
188 ZAttributeRun *run = [_attributes objectAtIndex:pivot];
189 if (run.index < enumerationRange.location) {
190 first = pivot;
191 } else if (run.index >= NSMaxRange(enumerationRange)) {
192 last = pivot;
193 }
194 }
195 attrs = [_attributes subarrayWithRange:NSMakeRange(first, last-first)];
196 }
197 if (opts & ZAttributedStringEnumerationReverse) {
198 NSUInteger end = [_buffer length];
199 for (ZAttributeRun *run in [attrs reverseObjectEnumerator]) {
200 BOOL stop = NO;
201 NSUInteger start = run.index;
202 // clip to enumerationRange
203 start = MAX(start, enumerationRange.location);
204 end = MIN(end, NSMaxRange(enumerationRange));
205 block(run.attributes, NSMakeRange(start, end - start), &stop);
206 if (stop) break;
207 end = run.index;
208 }
209 } else {
210 NSUInteger start = 0;
211 ZAttributeRun *run = [attrs objectAtIndex:0];
212 NSInteger offset = 0;
213 NSInteger oldLength = [_buffer length];
214 for (NSUInteger i = 1;;i++) {
215 NSUInteger end;
216 if (i >= [attrs count]) {
217 end = oldLength;
218 } else {
219 end = [[attrs objectAtIndex:i] index];
220 }
221 BOOL stop = NO;
222 NSUInteger clippedStart = MAX(start, enumerationRange.location);
223 NSUInteger clippedEnd = MIN(end, NSMaxRange(enumerationRange));
224 block(run.attributes, NSMakeRange(clippedStart + offset, clippedEnd - start), &stop);
225 if (stop || i >= [attrs count]) break;
226 start = end;
227 NSUInteger newLength = [_buffer length];
228 offset += (newLength - oldLength);
229 oldLength = newLength;
230 }
231 }
232}
233#endif
234
235- (BOOL)isEqualToAttributedString:(ZAttributedString *)otherString {
236 return ([_buffer isEqualToString:otherString->_buffer] && [_attributes isEqualToArray:otherString->_attributes]);
237}
238
239- (BOOL)isEqual:(id)object {
240 return [object isKindOfClass:[ZAttributedString class]] && [self isEqualToAttributedString:(ZAttributedString *)object];
241}
242
243#pragma mark -
244
245- (NSUInteger)indexOfEffectiveAttributeRunForIndex:(NSUInteger)index {
246 NSUInteger first = 0, last = [_attributes count];
247 while (last > first + 1) {
248 NSUInteger pivot = (last + first) / 2;
249 ZAttributeRun *run = [_attributes objectAtIndex:pivot];
250 if (run.index > index) {
251 last = pivot;
252 } else if (run.index < index) {
253 first = pivot;
254 } else {
255 first = pivot;
256 break;
257 }
258 }
259 return first;
260}
261
262- (NSDictionary *)attributesAtIndex:(NSUInteger)index effectiveRange:(NSRangePointer)aRange uniquingOnName:(NSString *)attributeName {
263 if (index >= [_buffer length]) {
264 @throw [NSException exceptionWithName:NSRangeException reason:@"index beyond range of attributed string" userInfo:nil];
265 }
266 NSUInteger runIndex = [self indexOfEffectiveAttributeRunForIndex:index];
267 ZAttributeRun *run = [_attributes objectAtIndex:runIndex];
268 if (aRange != NULL) {
269 aRange->location = run.index;
270 runIndex++;
271 if (runIndex < [_attributes count]) {
272 aRange->length = [[_attributes objectAtIndex:runIndex] index] - aRange->location;
273 } else {
274 aRange->length = [_buffer length] - aRange->location;
275 }
276 }
277 return run.attributes;
278}
279- (NSDictionary *)attributesAtIndex:(NSUInteger)index longestEffectiveRange:(NSRangePointer)aRange
280 inRange:(NSRange)rangeLimit uniquingOnName:(NSString *)attributeName {
281 if (index >= [_buffer length]) {
282 @throw [NSException exceptionWithName:NSRangeException reason:@"index beyond range of attributed string" userInfo:nil];
283 } else if (NSMaxRange(rangeLimit) > [_buffer length]) {
284 @throw [NSException exceptionWithName:NSRangeException reason:@"rangeLimit beyond range of attributed string" userInfo:nil];
285 }
286 NSUInteger runIndex = [self indexOfEffectiveAttributeRunForIndex:index];
287 ZAttributeRun *run = [_attributes objectAtIndex:runIndex];
288 if (aRange != NULL) {
289 if (attributeName != nil) {
290 id value = [run.attributes objectForKey:attributeName];
291 NSUInteger endRunIndex = runIndex+1;
292 runIndex--;
293 // search backwards
294 while (1) {
295 if (run.index <= rangeLimit.location) {
296 break;
297 }
298 ZAttributeRun *prevRun = [_attributes objectAtIndex:runIndex];
299 id prevValue = [prevRun.attributes objectForKey:attributeName];
300 if (prevValue == value || (value != nil && [prevValue isEqual:value])) {
301 runIndex--;
302 run = prevRun;
303 } else {
304 break;
305 }
306 }
307 // search forwards
308 ZAttributeRun *endRun = nil;
309 while (endRunIndex < [_attributes count]) {
310 ZAttributeRun *nextRun = [_attributes objectAtIndex:endRunIndex];
311 if (nextRun.index >= NSMaxRange(rangeLimit)) {
312 endRun = nextRun;
313 break;
314 }
315 id nextValue = [nextRun.attributes objectForKey:attributeName];
316 if (nextValue == value || (value != nil && [nextValue isEqual:value])) {
317 endRunIndex++;
318 } else {
319 endRun = nextRun;
320 break;
321 }
322 }
323 aRange->location = MAX(run.index, rangeLimit.location);
324 aRange->length = MIN((endRun ? endRun.index : [_buffer length]), NSMaxRange(rangeLimit)) - aRange->location;
325 } else {
326 // with no attribute name, we don't need to do any real searching,
327 // as we already guarantee each run has unique attributes.
328 // just make sure to clip the range to the rangeLimit
329 aRange->location = MAX(run.index, rangeLimit.location);
330 ZAttributeRun *endRun = (runIndex+1 < [_attributes count] ? [_attributes objectAtIndex:runIndex+1] : nil);
331 aRange->length = MIN((endRun ? endRun.index : [_buffer length]), NSMaxRange(rangeLimit)) - aRange->location;
332 }
333 }
334 return run.attributes;
335}
336
337- (void)dealloc {
338 [_buffer release];
339 [_attributes release];
340 [super dealloc];
341}
342@end
343
344@interface ZMutableAttributedString ()
345- (void)cleanupAttributesInRange:(NSRange)range;
346- (NSRange)rangeOfAttributeRunsForRange:(NSRange)range;
347- (void)offsetRunsInRange:(NSRange )range byOffset:(NSInteger)offset;
348@end
349
350@implementation ZMutableAttributedString
351- (id)copyWithZone:(NSZone *)zone {
352 return [(ZAttributedString *)[ZAttributedString allocWithZone:zone] initWithAttributedString:self];
353}
354
355- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)range {
356 range = [self rangeOfAttributeRunsForRange:range];
357 for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
358 [run.attributes setObject:value forKey:name];
359 }
360 [self cleanupAttributesInRange:range];
361}
362
363- (void)addAttributes:(NSDictionary *)attributes range:(NSRange)range {
364 range = [self rangeOfAttributeRunsForRange:range];
365 for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
366 [run.attributes addEntriesFromDictionary:attributes];
367 }
368 [self cleanupAttributesInRange:range];
369}
370
371- (void)appendAttributedString:(ZAttributedString *)str {
372 [self insertAttributedString:str atIndex:[_buffer length]];
373}
374
375- (void)deleteCharactersInRange:(NSRange)range {
376 NSRange runRange = [self rangeOfAttributeRunsForRange:range];
377 [_buffer replaceCharactersInRange:range withString:@""];
378 [_attributes removeObjectsInRange:runRange];
379 for (NSUInteger i = runRange.location; i < [_attributes count]; i++) {
380 ZAttributeRun *run = [_attributes objectAtIndex:i];
381 ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:(run.index - range.length) attributes:run.attributes];
382 [_attributes replaceObjectAtIndex:i withObject:newRun];
383 [newRun release];
384 }
385 [self cleanupAttributesInRange:NSMakeRange(runRange.location, 0)];
386}
387
388- (void)insertAttributedString:(ZAttributedString *)str atIndex:(NSUInteger)idx {
389 [self replaceCharactersInRange:NSMakeRange(idx, 0) withAttributedString:str];
390}
391
392- (void)removeAttribute:(NSString *)name range:(NSRange)range {
393 range = [self rangeOfAttributeRunsForRange:range];
394 for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
395 [run.attributes removeObjectForKey:name];
396 }
397 [self cleanupAttributesInRange:range];
398}
399
400- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(ZAttributedString *)str {
401 NSRange replaceRange = [self rangeOfAttributeRunsForRange:range];
402 NSInteger offset = [str->_buffer length] - range.length;
403 [_buffer replaceCharactersInRange:range withString:str->_buffer];
404 [_attributes replaceObjectsInRange:replaceRange withObjectsFromArray:str->_attributes];
405 NSRange newRange = NSMakeRange(replaceRange.location, [str->_attributes count]);
406 [self offsetRunsInRange:newRange byOffset:range.location];
407 [self offsetRunsInRange:NSMakeRange(NSMaxRange(newRange), [_attributes count] - NSMaxRange(newRange)) byOffset:offset];
408 [self cleanupAttributesInRange:NSMakeRange(newRange.location, 0)];
409 [self cleanupAttributesInRange:NSMakeRange(NSMaxRange(newRange), 0)];
410}
411
412- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str {
413 [self replaceCharactersInRange:range withAttributedString:[[[ZAttributedString alloc] initWithString:str] autorelease]];
414}
415
416- (void)setAttributedString:(ZAttributedString *)str {
417 [_buffer release], _buffer = [str->_buffer mutableCopy];
418 [_attributes release], _attributes = [str->_attributes mutableCopy];
419}
420
421- (void)setAttributes:(NSDictionary *)attributes range:(NSRange)range {
422 range = [self rangeOfAttributeRunsForRange:range];
423 for (ZAttributeRun *run in [_attributes subarrayWithRange:range]) {
424 [run.attributes setDictionary:attributes];
425 }
426 [self cleanupAttributesInRange:range];
427}
428
429#pragma mark -
430
431// splits the existing runs to provide one or more new runs for the given range
432- (NSRange)rangeOfAttributeRunsForRange:(NSRange)range {
433 NSParameterAssert(NSMaxRange(range) <= [_buffer length]);
434
435 // find (or create) the first run
436 NSUInteger first = 0;
437 ZAttributeRun *lastRun = nil;
438 for (;;first++) {
439 if (first >= [_attributes count]) {
440 // we didn't find a run
441 first = [_attributes count];
442 ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:range.location attributes:lastRun.attributes];
443 [_attributes addObject:newRun];
444 [newRun release];
445 break;
446 }
447 ZAttributeRun *run = [_attributes objectAtIndex:first];
448 if (run.index == range.location) {
449 break;
450 } else if (run.index > range.location) {
451 ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:range.location attributes:lastRun.attributes];
452 [_attributes insertObject:newRun atIndex:first];
453 [newRun release];
454 break;
455 }
456 lastRun = run;
457 }
458
459 if (((ZAttributeRun *)[_attributes lastObject]).index < NSMaxRange(range)) {
460 NSRange subrange = NSMakeRange(first, [_attributes count] - first);
461 if (NSMaxRange(range) < [_buffer length]) {
462 ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:NSMaxRange(range)
463 attributes:(NSDictionary*)[(ZAttributeRun *)[_attributes lastObject] attributes]];
464 [_attributes addObject:newRun];
465 [newRun release];
466 }
467 return subrange;
468 } else {
469 // find the last run within and the first run after the range
470 NSUInteger lastIn = first, firstAfter = [_attributes count]-1;
471 while (firstAfter > lastIn + 1) {
472 NSUInteger idx = (firstAfter + lastIn) / 2;
473 ZAttributeRun *run = [_attributes objectAtIndex:idx];
474 if (run.index < range.location) {
475 lastIn = idx;
476 } else if (run.index > range.location) {
477 firstAfter = idx;
478 } else {
479 // this is definitively the first run after the range
480 firstAfter = idx;
481 break;
482 }
483 }
484 if ([[_attributes objectAtIndex:firstAfter] index] > NSMaxRange(range)) {
485 // the first after is too far after, insert another run!
486 ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:NSMaxRange(range)
487 attributes:[(ZAttributeRun *)[_attributes objectAtIndex:firstAfter-1] attributes]];
488 [_attributes insertObject:newRun atIndex:firstAfter];
489 [newRun release];
490 }
491 return NSMakeRange(lastIn, firstAfter - lastIn);
492 }
493}
494
495- (void)cleanupAttributesInRange:(NSRange)range {
496 // expand the range to include one surrounding attribute on each side
497 if (range.location > 0) {
498 range.location -= 1;
499 range.length += 1;
500 }
501 if (NSMaxRange(range) < [_attributes count]) {
502 range.length += 1;
503 } else {
504 // make sure the range is capped to the attributes count
505 range.length = [_attributes count] - range.location;
506 }
507 if (range.length == 0) return;
508 ZAttributeRun *lastRun = [_attributes objectAtIndex:range.location];
509 for (NSUInteger i = range.location+1; i < NSMaxRange(range);) {
510 ZAttributeRun *run = [_attributes objectAtIndex:i];
511 if ([lastRun.attributes isEqualToDictionary:run.attributes]) {
512 [_attributes removeObjectAtIndex:i];
513 range.length -= 1;
514 } else {
515 lastRun = run;
516 i++;
517 }
518 }
519}
520
521- (void)offsetRunsInRange:(NSRange)range byOffset:(NSInteger)offset {
522 for (NSUInteger i = range.location; i < NSMaxRange(range); i++) {
523 ZAttributeRun *run = [_attributes objectAtIndex:i];
524 ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:run.index + offset attributes:run.attributes];
525 [_attributes replaceObjectAtIndex:i withObject:newRun];
526 [newRun release];
527 }
528}
529@end
530
531@implementation ZAttributeRun
532@synthesize index = _index;
533@synthesize attributes = _attributes;
534
535+ (id)attributeRunWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs {
536 return [[[self alloc] initWithIndex:idx attributes:attrs] autorelease];
537}
538
539- (id)initWithIndex:(NSUInteger)idx attributes:(NSDictionary *)attrs {
540 NSParameterAssert(idx >= 0);
541 if ((self = [super init])) {
542 _index = idx;
543 if (attrs == nil) {
544 _attributes = [[NSMutableDictionary alloc] init];
545 } else {
546 _attributes = [attrs mutableCopy];
547 }
548 }
549 return self;
550}
551
552- (id)initWithCoder:(NSCoder *)decoder {
553 if ((self = [super init])) {
554 _index = [[decoder decodeObjectForKey:@"index"] unsignedIntegerValue];
555 _attributes = [[decoder decodeObjectForKey:@"attributes"] mutableCopy];
556 }
557 return self;
558}
559
560- (id)init {
561 return [self initWithIndex:0 attributes:[NSDictionary dictionary]];
562}
563
564- (id)copyWithZone:(NSZone *)zone {
565 return [[ZAttributeRun allocWithZone:zone] initWithIndex:_index attributes:_attributes];
566}
567
568- (void)encodeWithCoder:(NSCoder *)aCoder {
569 [aCoder encodeObject:[NSNumber numberWithUnsignedInteger:_index] forKey:@"index"];
570 [aCoder encodeObject:_attributes forKey:@"attributes"];
571}
572
573- (NSString *)description {
574 NSMutableArray *components = [NSMutableArray arrayWithCapacity:[_attributes count]];
575 for (id key in _attributes) {
576 [components addObject:[NSString stringWithFormat:@"%@=%@", key, [_attributes objectForKey:key]]];
577 }
578 return [NSString stringWithFormat:@"<%@: %p index=%lu attributes={%@}>",
579 NSStringFromClass([self class]), self, (unsigned long)_index, [components componentsJoinedByString:@" "]];
580}
581
582- (BOOL)isEqual:(id)object {
583 if (![object isKindOfClass:[ZAttributeRun class]]) return NO;
584 ZAttributeRun *other = (ZAttributeRun *)object;
585 return _index == other->_index && [_attributes isEqualToDictionary:other->_attributes];
586}
587
588- (void)dealloc {
589 [_attributes release];
590 [super dealloc];
591}
592@end
593
594NSString * const ZFontAttributeName = @"ZFontAttributeName";
595NSString * const ZForegroundColorAttributeName = @"ZForegroundColorAttributeName";
596NSString * const ZBackgroundColorAttributeName = @"ZBackgroundColorAttributeName";
597NSString * const ZUnderlineStyleAttributeName = @"ZUnderlineStyleAttributeName";