root/trunk/Subtitles/SubATSUIRenderer.m

Revision 919, 42.1 kB (checked in by astrange, 2 months ago)

SSA: Support vertical text (fonts with @ in the name).

Line 
1 //
2 //  SubATSUIRenderer.m
3 //  SSARender2
4 //
5 //  Created by Alexander Strange on 7/30/07.
6 //  Copyright 2007 __MyCompanyName__. All rights reserved.
7 //
8
9 #import "SubATSUIRenderer.h"
10 #import "SubImport.h"
11 #import "SubParsing.h"
12 #import "SubUtilities.h"
13 #import "Codecprintf.h"
14
15 static float GetWinFontSizeScale(ATSFontRef font);
16 static void FindAllPossibleLineBreaks(TextBreakLocatorRef breakLocator, unichar *uline, UniCharArrayOffset lineLen, unsigned char *breakOpportunities);
17
18 #define declare_bitfield(name, bits) unsigned char name[bits / 8 + 1]; bzero(name, sizeof(name));
19 #define bitfield_set(name, bit) name[(bit) / 8] |= 1 << ((bit) % 8);
20 #define bitfield_test(name, bit) ((name[(bit) / 8] & (1 << ((bit) % 8))) != 0)
21
22 @interface SubATSUISpanEx : NSObject {
23         @public;
24         ATSUStyle style;
25         CGColorRef primaryColor, outlineColor, shadowColor;
26         Float32 outlineRadius, shadowDist, scaleX, scaleY, primaryAlpha, outlineAlpha, angle;
27         BOOL blurEdges;
28 }
29 -(SubATSUISpanEx*)initWithStyle:(ATSUStyle)style_ subStyle:(SubStyle*)sstyle colorSpace:(CGColorSpaceRef)cs;
30 -(SubATSUISpanEx*)clone;
31 @end
32
33 @implementation SubATSUISpanEx
34 -(void)dealloc
35 {
36         ATSUDisposeStyle(style);
37         CGColorRelease(primaryColor);
38         CGColorRelease(outlineColor);
39         CGColorRelease(shadowColor);
40         [super dealloc];
41 }
42
43 -(void)finalize
44 {
45         ATSUDisposeStyle(style);
46         CGColorRelease(primaryColor);
47         CGColorRelease(outlineColor);
48         CGColorRelease(shadowColor);
49         [super finalize];
50 }
51
52 static CGColorRef MakeCGColorFromRGBA(SubRGBAColor c, CGColorSpaceRef cspace)
53 {
54         const float components[] = {c.red, c.green, c.blue, c.alpha};
55        
56         return CGColorCreate(cspace, components);
57 }
58
59 static CGColorRef MakeCGColorFromRGBOpaque(SubRGBAColor c, CGColorSpaceRef cspace)
60 {
61         SubRGBAColor c2 = c;
62         c2.alpha = 1;
63         return MakeCGColorFromRGBA(c2, cspace);
64 }
65
66 static CGColorRef CloneCGColorWithAlpha(CGColorRef c, float alpha)
67 {
68         CGColorRef new = CGColorCreateCopyWithAlpha(c, alpha);
69         CGColorRelease(c);
70         return new;
71 }
72
73 -(SubATSUISpanEx*)initWithStyle:(ATSUStyle)style_ subStyle:(SubStyle*)sstyle colorSpace:(CGColorSpaceRef)cs
74 {
75         if (self = [super init]) {
76                 ATSUCreateAndCopyStyle(style_, &style);
77                
78                 primaryColor = MakeCGColorFromRGBOpaque(sstyle->primaryColor, cs);
79                 primaryAlpha = sstyle->primaryColor.alpha;
80                 outlineColor = MakeCGColorFromRGBOpaque(sstyle->outlineColor, cs);
81                 outlineAlpha = sstyle->outlineColor.alpha;
82                 shadowColor  = MakeCGColorFromRGBA(sstyle->shadowColor,  cs);
83                 outlineRadius = sstyle->outlineRadius;
84                 shadowDist = sstyle->shadowDist;
85                 scaleX = sstyle->scaleX / 100.;
86                 scaleY = sstyle->scaleY / 100.;
87                 angle = sstyle->angle;
88         }
89        
90         return self;
91 }
92
93 -(SubATSUISpanEx*)clone
94 {
95         SubATSUISpanEx *ret = [[SubATSUISpanEx alloc] init];
96        
97         ATSUCreateAndCopyStyle(style, &ret->style);
98         ret->primaryColor = CGColorRetain(primaryColor);
99         ret->primaryAlpha = primaryAlpha;
100         ret->outlineColor = CGColorRetain(outlineColor);
101         ret->outlineAlpha = outlineAlpha;
102         ret->shadowColor = CGColorRetain(shadowColor);
103         ret->outlineRadius = outlineRadius;
104         ret->shadowDist = shadowDist;
105         ret->scaleX = scaleX;
106         ret->scaleY = scaleY;
107         ret->angle = angle;
108        
109         return [ret autorelease];
110 }
111 @end
112
113 #define span_ex(span) ((SubATSUISpanEx*)span->ex)
114
115 static void SetATSUStyleFlag(ATSUStyle style, ATSUAttributeTag t, Boolean v)
116 {
117         const ATSUAttributeTag tags[] = {t};
118         const ByteCount          sizes[] = {sizeof(v)};
119         const ATSUAttributeValuePtr vals[] = {&v};
120        
121         ATSUSetAttributes(style,1,tags,sizes,vals);
122 }
123
124 static void SetATSUStyleOther(ATSUStyle style, ATSUAttributeTag t, ByteCount s, const ATSUAttributeValuePtr v)
125 {
126         const ATSUAttributeTag tags[] = {t};
127         const ByteCount          sizes[] = {s};
128         const ATSUAttributeValuePtr vals[] = {v};
129        
130         ATSUSetAttributes(style,1,tags,sizes,vals);
131 }
132
133 static void SetATSULayoutOther(ATSUTextLayout l, ATSUAttributeTag t, ByteCount s, const ATSUAttributeValuePtr v)
134 {
135         const ATSUAttributeTag tags[] = {t};
136         const ByteCount          sizes[] = {s};
137         const ATSUAttributeValuePtr vals[] = {v};
138        
139         ATSUSetLayoutControls(l,1,tags,sizes,vals);
140 }
141
142 @implementation SubATSUIRenderer
143
144 // from Apple Q&A 1396
145 static CGColorSpaceRef CreateICCColorSpaceFromPathToProfile (const char * iccProfilePath) {
146         CMProfileRef    iccProfile = NULL;
147         CGColorSpaceRef iccColorSpace = NULL;
148         CMProfileLocation loc;
149        
150         // Specify that the location of the profile will be a POSIX path to the profile.
151         loc.locType = cmPathBasedProfile;
152        
153         // Make sure the path is not larger then the buffer
154         if(strlen(iccProfilePath) > sizeof(loc.u.pathLoc.path))
155                 return NULL;
156        
157         // Copy the path the profile into the CMProfileLocation structure
158         strcpy (loc.u.pathLoc.path, iccProfilePath);
159        
160         // Open the profile
161         if (CMOpenProfile(&iccProfile, &loc) != noErr)
162         {
163                 iccProfile = (CMProfileRef) 0;
164                 return NULL;
165         }
166        
167         // Create the ColorSpace with the open profile.
168         iccColorSpace = CGColorSpaceCreateWithPlatformColorSpace( iccProfile );
169        
170         // Close the profile now that we have what we need from it.
171         CMCloseProfile(iccProfile);
172        
173         return iccColorSpace;
174 }
175
176 static CGColorSpaceRef CreateColorSpaceFromSystemICCProfileName(CFStringRef profileName) {
177         FSRef pathToProfilesFolder;
178     FSRef pathToProfile;
179        
180         // Find the Systems Color Sync Profiles folder
181         if(FSFindFolder(kOnSystemDisk, kColorSyncProfilesFolderType,
182                                         kDontCreateFolder, &pathToProfilesFolder) == noErr) {
183                
184                 // Make a UniChar string of the profile name
185                 UniChar uniBuffer[sizeof(CMPathLocation)];
186                 CFStringGetCharacters (profileName,CFRangeMake(0,CFStringGetLength(profileName)),uniBuffer);
187                
188                 // Create a FSRef to the profile in the Systems Color Sync Profile folder
189                 if(FSMakeFSRefUnicode (&pathToProfilesFolder,CFStringGetLength(profileName),uniBuffer,
190                                                            kUnicodeUTF8Format,&pathToProfile) == noErr) {
191                         unsigned char path[sizeof(CMPathLocation)];
192                        
193                         // Write the posix path to the profile into our path buffer from the FSRef
194                         if(FSRefMakePath (&pathToProfile,path,sizeof(CMPathLocation)) == noErr)
195                                 return CreateICCColorSpaceFromPathToProfile((char*)path);
196                 }
197         }
198        
199         return NULL;
200 }
201
202 static CGColorSpaceRef CreateICCsRGBColorSpace() {
203         return CreateColorSpaceFromSystemICCProfileName(CFSTR("sRGB Profile.icc"));
204 }
205
206 static CGColorSpaceRef GetSRGBColorSpace() {
207         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
208         CGColorSpaceRef cs = CreateICCsRGBColorSpace();
209         [pool release];
210         return cs;
211 }
212
213 -(SubATSUIRenderer*)initWithVideoWidth:(float)width videoHeight:(float)height;
214 {
215         if (self = [super init]) {
216                 ATSUCreateTextLayout(&layout);
217                 ubuffer = malloc(sizeof(unichar) * 128);
218                 srgbCSpace = GetSRGBColorSpace();
219                
220                 videoWidth = width;
221                 videoHeight = height;
222                
223                 UCCreateTextBreakLocator(NULL, 0, kUCTextBreakLineMask, &breakLocator);
224                 [[SubContext alloc] initWithNonSSAType:kSubTypeSRT delegate:self];
225         }
226        
227         return self;
228 }
229
230 -(SubATSUIRenderer*)initWithSSAHeader:(NSString*)header videoWidth:(float)width videoHeight:(float)height;
231 {
232         if (self = [super init]) {
233                 unsigned hlength = [header length];
234                 unichar *uheader = malloc(sizeof(unichar) * hlength);
235                
236                 header = STStandardizeStringNewlines(header);
237                 [header getCharacters:uheader];
238                
239                 NSDictionary *headers;
240                 NSArray *styles;
241                 SubParseSSAFile(uheader, hlength, &headers, &styles, NULL);
242                 free(uheader);
243                
244                 ubuffer = malloc(sizeof(unichar) * 128);
245                
246                 videoWidth = width;
247                 videoHeight = height;
248                
249                 ATSUCreateTextLayout(&layout);
250                 srgbCSpace = GetSRGBColorSpace();
251                
252                 UCCreateTextBreakLocator(NULL, 0, kUCTextBreakLineMask, &breakLocator);
253                 [[SubContext alloc] initWithHeaders:headers styles:styles extraData:header delegate:self];
254         }
255        
256         return self;
257 }
258
259 -(void)dealloc
260 {
261         [context release];
262         free(ubuffer);
263         CGColorSpaceRelease(srgbCSpace);
264         UCDisposeTextBreakLocator(&breakLocator);
265         ATSUDisposeTextLayout(layout);
266         [super dealloc];
267 }
268
269 -(void)finalize
270 {
271         free(ubuffer);
272         UCDisposeTextBreakLocator(&breakLocator);
273         ATSUDisposeTextLayout(layout);
274         [super finalize];
275 }
276
277 -(void)completedHeaderParsing:(SubContext*)sc
278 {
279         context = sc;
280         screenScaleX = videoWidth / context->resX;
281         screenScaleY = videoHeight / context->resY;
282 }
283
284 -(float)aspectRatio
285 {
286         return videoWidth / videoHeight;
287 }
288
289 static NSMutableDictionary *fontIDCache = nil;
290 static ATSUFontID fontCount=-1, *fontIDs = NULL;
291
292 static void CleanupFontIDCache() __attribute__((destructor));
293 static void CleanupFontIDCache()
294 {
295         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
296         if (fontIDCache) [fontIDCache release];
297         if (fontIDs) free(fontIDs);
298         fontIDCache = nil;
299         fontIDs = NULL;
300         [pool release];
301 }
302
303 // XXX: Assumes ATSUFontID = ATSFontRef. This is true.
304 static ATSUFontID GetFontIDForSSAName(NSString *name)
305 {
306         ByteCount nlen = [name length];
307         unichar *uname = (unichar*)[name cStringUsingEncoding:NSUnicodeStringEncoding];
308        
309         if (!fontIDCache) fontIDCache = [[NSMutableDictionary alloc] init];
310        
311         NSNumber *idN = [fontIDCache objectForKey:[name lowercaseString]];
312                
313         if (idN) return [idN intValue];
314        
315         ATSUFontID font;
316        
317         ATSUFindFontFromName(uname, nlen * sizeof(unichar), kFontFamilyName, kFontNoPlatformCode, kFontNoScript, kFontNoLanguage, &font);
318        
319         if (font == kATSUInvalidFontID) font = ATSFontFindFromName((CFStringRef)name, kATSOptionFlagsDefault); // for bugs in ATS under 10.4
320         if (font == kATSUInvalidFontID) { // try a case-insensitive search
321                 if (fontCount == -1) {
322                         ATSUFontCount(&fontCount);
323                         fontIDs = malloc(sizeof(ATSUFontID[fontCount]));
324                         ATSUGetFontIDs(fontIDs, fontCount, &fontCount);
325                 }
326                                
327                 ByteCount len;
328                 ItemCount x, index;
329                 const ByteCount kBufLength = 1024/sizeof(unichar);
330                 unichar buf[kBufLength];
331          
332                 for (x = 0; x < fontCount; x++) {
333                         ATSUFindFontName(fontIDs[x], kFontFamilyName, kFontMicrosoftPlatform, kFontNoScript, kFontNoLanguage, kBufLength, (Ptr)buf, &len, &index);
334                         NSString *fname = [NSString stringWithCharacters:buf length:len/sizeof(unichar)];
335                        
336                         if ([name caseInsensitiveCompare:fname] == NSOrderedSame) {
337                                 font = fontIDs[x];
338                                 break;
339                         }
340                 }
341                
342                 if (font == kATSUInvalidFontID) font = ATSFontFindFromName((CFStringRef)@"Helvetica",kATSOptionFlagsDefault); // final fallback
343         }
344        
345         [fontIDCache setValue:[NSNumber numberWithInt:font] forKey:[name lowercaseString]];
346          
347         return font;
348 }
349
350 -(void*)completedStyleParsing:(SubStyle*)s
351 {
352         const ATSUAttributeTag tags[] = {kATSUStyleRenderingOptionsTag, kATSUSizeTag, kATSUQDBoldfaceTag, kATSUQDItalicTag, kATSUQDUnderlineTag, kATSUStyleStrikeThroughTag, kATSUFontTag, kATSUVerticalCharacterTag};
353         const ByteCount          sizes[] = {sizeof(ATSStyleRenderingOptions), sizeof(Fixed), sizeof(Boolean), sizeof(Boolean), sizeof(Boolean), sizeof(Boolean), sizeof(ATSUFontID), sizeof(ATSUVerticalCharacterType)};
354        
355         NSString *fn = s->fontname;
356         ATSUVerticalCharacterType vertical = ParseFontVerticality(&fn) ? kATSUStronglyVertical : kATSUStronglyHorizontal;
357         ATSUFontID font = GetFontIDForSSAName(fn);
358         ATSFontRef fontRef = font;
359         ATSStyleRenderingOptions opt = kATSStyleApplyAntiAliasing;
360         Fixed size;
361         Boolean b = s->bold, i = s->italic, u = s->underline, st = s->strikeout;
362         ATSUStyle style;
363                
364         const ATSUAttributeValuePtr vals[] = {&opt, &size, &b, &i, &u, &st, &font, &vertical};
365        
366         if (!s->platformSizeScale) s->platformSizeScale = GetWinFontSizeScale(fontRef);
367         size = FloatToFixed(s->size * s->platformSizeScale * screenScaleY);
368        
369         ATSUCreateStyle(&style);
370         ATSUSetAttributes(style, sizeof(tags) / sizeof(ATSUAttributeTag), tags, sizes, vals);
371        
372         if (s->tracking > 0) { // bug in VSFilter: negative tracking in style lines is ignored
373                 Fixed tracking = FloatToFixed(s->tracking);
374                
375                 SetATSUStyleOther(style, kATSUAfterWithStreamShiftTag, sizeof(Fixed), &tracking);
376         }
377        
378         if (s->scaleX != 100. || s->scaleY != 100.) {
379                 CGAffineTransform mat = CGAffineTransformMakeScale(s->scaleX / 100., s->scaleY / 100.);
380                
381                 SetATSUStyleOther(style, kATSUFontMatrixTag, sizeof(CGAffineTransform), &mat);
382         }
383        
384         const ATSUFontFeatureType ftypes[] = {kLigaturesType, kTypographicExtrasType, kTypographicExtrasType, kTypographicExtrasType};
385         const ATSUFontFeatureSelector fsels[] = {kCommonLigaturesOnSelector, kSmartQuotesOnSelector, kPeriodsToEllipsisOnSelector, kHyphenToEnDashOnSelector};
386        
387         ATSUSetFontFeatures(style, sizeof(ftypes) / sizeof(ATSUFontFeatureType), ftypes, fsels);
388        
389         return style;
390 }
391
392 -(void)releaseStyleExtra:(void*)ex
393 {
394         ATSUDisposeStyle(ex);
395 }
396
397 -(void*)spanExtraFromRenderDiv:(SubRenderDiv*)div
398 {
399         return [[[SubATSUISpanEx alloc] initWithStyle:(ATSUStyle)div->styleLine->ex subStyle:div->styleLine colorSpace:srgbCSpace] autorelease];
400 }
401
402 -(void*)cloneSpanExtra:(SubRenderSpan*)span
403 {
404         return [(SubATSUISpanEx*)span->ex clone];
405 }
406
407 -(void)disposeSpanExtra:(void*)ex
408 {
409         SubATSUISpanEx *spanEx = ex;
410         [spanEx release];
411 }
412
413 enum {renderMultipleParts = 1, // call ATSUDrawText more than once, needed for color/border changes in the middle of lines
414           renderManualShadows = 2, // CG shadows can't change inside a line... probably
415           renderComplexTransforms = 4}; // can't draw text at all, have to transform each vertex. needed for 3D perspective, or \frz in the middle of a line
416
417 -(void)spanChangedTag:(SSATagType)tag span:(SubRenderSpan*)span div:(SubRenderDiv*)div param:(void*)p
418 {
419         SubATSUISpanEx *spanEx = span->ex;
420         BOOL isFirstSpan = [div->spans count] == 0;
421         Boolean bval;
422         int ival;
423         float fval;
424         NSString *sval;
425         Fixed fixval;
426         CGColorRef color;
427         CGAffineTransform mat;
428        
429 #define bv() bval = *(int*)p;
430 #define iv() ival = *(int*)p;
431 #define fv() fval = *(float*)p;
432 #define sv() sval = *(NSString**)p;
433 #define fixv() fv(); fixval = FloatToFixed(fval);
434 #define colorv() color = MakeCGColorFromRGBA(ParseSSAColor(*(int*)p), srgbCSpace);
435        
436         switch (tag) {
437                 case tag_b:
438                         bv();
439                         SetATSUStyleFlag(spanEx->style, kATSUQDBoldfaceTag, bval != 0);
440                         break;
441                 case tag_i:
442                         bv();
443                         SetATSUStyleFlag(spanEx->style, kATSUQDItalicTag, bval);
444                         break;
445                 case tag_u:
446                         bv();
447                         SetATSUStyleFlag(spanEx->style, kATSUQDUnderlineTag, bval);
448                         break;
449                 case tag_s:
450                         bv();
451                         SetATSUStyleFlag(spanEx->style, kATSUStyleStrikeThroughTag, bval);
452                         break;
453                 case tag_bord:
454                         fv();
455                         if (!isFirstSpan) div->render_complexity |= renderMultipleParts;
456                         spanEx->outlineRadius = fval;
457                         break;
458                 case tag_shad:
459                         fv();
460                         if (!isFirstSpan) div->render_complexity |= renderMultipleParts | renderManualShadows;
461                         spanEx->shadowDist = fval;
462                         break;
463                 case tag_fn:
464                         sv();
465                         {
466                                 ATSUVerticalCharacterType vertical = ParseFontVerticality(&sval) ? kATSUStronglyVertical : kATSUStronglyHorizontal;
467                                 ATSUFontID font = GetFontIDForSSAName(sval);
468                                
469                                 if (font) {
470                                         SetATSUStyleFlag(spanEx->style, kATSUVerticalCharacterTag, vertical);
471                                         SetATSUStyleOther(spanEx->style, kATSUFontTag, sizeof(ATSUFontID), &font);
472                                 }
473                         }
474                         break;
475                 case tag_fs:
476                         fv();
477                         fixval = FloatToFixed(fval * div->styleLine->platformSizeScale);
478                         SetATSUStyleOther(spanEx->style, kATSUSizeTag, sizeof(Fixed), &fixval);
479                         break;
480                 case tag_1c:
481                         CGColorRelease(spanEx->primaryColor);
482                         if (!isFirstSpan) div->render_complexity |= renderMultipleParts;
483                         colorv();
484                         spanEx->primaryColor = color;
485                         break;
486                 case tag_3c:
487                         CGColorRelease(spanEx->outlineColor);
488                         if (!isFirstSpan) div->render_complexity |= renderMultipleParts;
489                         {
490                                 SubRGBAColor rgba = ParseSSAColor(*(int*)p);
491                                 spanEx->outlineColor = MakeCGColorFromRGBOpaque(rgba, srgbCSpace);
492                                 spanEx->outlineAlpha = rgba.alpha;
493                         }
494                         break;
495                 case tag_4c:
496                         CGColorRelease(spanEx->shadowColor);
497                         if (!isFirstSpan) div->render_complexity |= renderMultipleParts | renderManualShadows;
498                         colorv();
499                         spanEx->shadowColor = color;
500                         break;
501                 case tag_fscx:
502                         fv();
503                         spanEx->scaleX = fval / 100.;
504                         mat = CGAffineTransformMakeScale(spanEx->scaleX, spanEx->scaleY);
505                         SetATSUStyleOther(spanEx->style, kATSUFontMatrixTag, sizeof(CGAffineTransform), &mat);
506                         break;
507                 case tag_fscy:
508                         fv();
509                         spanEx->scaleY = fval / 100.;
510                         mat = CGAffineTransformMakeScale(spanEx->scaleX, spanEx->scaleY);
511                         SetATSUStyleOther(spanEx->style, kATSUFontMatrixTag, sizeof(CGAffineTransform), &mat);
512                         break;
513                 case tag_fsp:
514                         fixv();
515                         SetATSUStyleOther(spanEx->style, kATSUAfterWithStreamShiftTag, sizeof(Fixed), &fixval);
516                         break;
517                 case tag_frz:
518                         if (!isFirstSpan) div->render_complexity |= renderComplexTransforms; // this one's hard
519                         fv();
520                         spanEx->angle = fval;
521                         break;
522                 case tag_1a:
523                         iv();
524                         if (!isFirstSpan) div->render_complexity |= renderMultipleParts;
525                         spanEx->primaryAlpha = (255-ival)/255.;
526                         break;
527                 case tag_3a:
528                         iv();
529                         if (!isFirstSpan) div->render_complexity |= renderMultipleParts;
530                         spanEx->outlineAlpha = (255-ival)/255.;
531                         break;
532                 case tag_4a:
533                         iv();
534                         if (!isFirstSpan) div->render_complexity |= renderMultipleParts | renderManualShadows;
535                         spanEx->shadowColor = CloneCGColorWithAlpha(spanEx->shadowColor, (255-ival)/255.);
536                         break;
537                 case tag_alpha:
538                         iv();
539                         fval = (255-ival)/255.;
540                         if (!isFirstSpan) div->render_complexity |= renderMultipleParts | renderManualShadows;
541                         spanEx->primaryAlpha = spanEx->outlineAlpha = fval;
542                         spanEx->shadowColor = CloneCGColorWithAlpha(spanEx->shadowColor, fval);
543                         break;
544                 case tag_r:
545                         sv();
546                         if (!isFirstSpan) div->render_complexity |= renderMultipleParts | renderManualShadows;
547                         {
548                                 SubStyle *style = [context->styles objectForKey:sval];
549                                 if (!style) style = div->styleLine;
550                                
551                                 [spanEx release];
552                                 span->ex = [[SubATSUISpanEx alloc] initWithStyle:(ATSUStyle)style->ex subStyle:style colorSpace:srgbCSpace];
553                         }
554                         break;
555                 case tag_be:
556                         bv();
557                         if (!isFirstSpan) div->render_complexity |= renderMultipleParts;
558                         spanEx->blurEdges = bval;
559                         break;
560                 default:
561                         Codecprintf(NULL, "Unimplemented SSA tag #%d\n",tag);
562         }
563 }
564
565 #pragma mark Rendering Helper Functions
566
567 static ATSUTextMeasurement GetLineHeight(ATSUTextLayout layout, UniCharArrayOffset lpos)
568 {
569         ATSUTextMeasurement ascent, descent;
570        
571         ATSUGetLineControl(layout, lpos, kATSULineAscentTag,  sizeof(ATSUTextMeasurement), &ascent,  NULL);
572         ATSUGetLineControl(layout, lpos, kATSULineDescentTag, sizeof(ATSUTextMeasurement), &descent, NULL);
573        
574         return ascent + descent;
575 }
576
577 static void ExpandCGRect(CGRect *rect, float radius)
578 {
579         rect->origin.x -= radius;
580         rect->origin.y -= radius;
581         rect->size.height += radius*2.;
582         rect->size.width += radius*2.;
583 }
584
585 static void GetTypographicRectangleForLayout(ATSUTextLayout layout, UniCharArrayOffset *breaks, ItemCount breakCount, Fixed extraHeight, Fixed *lX, Fixed *lY, Fixed *height, Fixed *width)
586 {
587         ATSTrapezoid trap = {0};
588         ItemCount trapCount;
589         FixedRect largeRect = {0};
590         Fixed baseY = 0;
591         int i;
592
593         for (i = breakCount; i >= 0; i--) {             
594                 UniCharArrayOffset end = breaks[i+1];
595                 FixedRect rect;
596                
597                 ATSUGetGlyphBounds(layout, 0, baseY, breaks[i], end-breaks[i], kATSUseDeviceOrigins, 1, &trap, &trapCount);
598
599                 baseY += GetLineHeight(layout, breaks[i]) + extraHeight;
600                
601                 rect.bottom = MAX(trap.lowerLeft.y, trap.lowerRight.y);
602                 rect.left = MIN(trap.lowerLeft.x, trap.upperLeft.x);
603                 rect.top = MIN(trap.upperLeft.y, trap.upperRight.y);
604                 rect.right = MAX(trap.lowerRight.x, trap.upperRight.x);
605                
606                 if (i == breakCount) largeRect = rect;
607                
608                 largeRect.bottom = MAX(largeRect.bottom, rect.bottom);
609                 largeRect.left = MIN(largeRect.left, rect.left);
610                 largeRect.top = MIN(largeRect.top, rect.top);
611                 largeRect.right = MAX(largeRect.right, rect.right);
612         }
613        
614         if (lX) *lX = largeRect.left;
615         if (lY) *lY = largeRect.bottom;
616         *height = largeRect.bottom - largeRect.top;
617         *width = largeRect.right - largeRect.left;
618 }
619
620 #if 0
621 static void GetImageBoundingBoxForLayout(ATSUTextLayout layout, UniCharArrayOffset *breaks, ItemCount breakCount, Fixed extraHeight, Fixed *lX, Fixed *lY, Fixed *height, Fixed *width)
622 {
623         Rect largeRect = {0};
624         ATSUTextMeasurement baseY = 0;
625         int i;
626        
627         for (i = breakCount; i >= 0; i--) {
628                 UniCharArrayOffset end = breaks[i+1];
629                 Rect rect;
630                
631                 ATSUMeasureTextImage(layout, breaks[i], end-breaks[i], 0, baseY, &rect);
632                
633                 baseY += GetLineHeight(layout, breaks[i]) + extraHeight;
634                
635                 if (i == breakCount) largeRect = rect;
636                
637                 largeRect.bottom = MAX(largeRect.bottom, rect.bottom);
638                 largeRect.left = MIN(largeRect.left, rect.left);
639                 largeRect.top = MIN(largeRect.top, rect.top);
640                 largeRect.right = MAX(largeRect.right, rect.right);
641                         }
642        
643        
644         if (lX) *lX = IntToFixed(largeRect.left);
645         if (lY) *lY = IntToFixed(largeRect.bottom);
646         *height = IntToFixed(largeRect.bottom - largeRect.top);
647         *width = IntToFixed(largeRect.right - largeRect.left);
648 }
649 #endif
650
651 enum {fillc, strokec};
652
653 static void SetColor(CGContextRef c, int whichcolor, CGColorRef col)
654 {
655         if (whichcolor == fillc) CGContextSetFillColorWithColor(c, col);
656         else CGContextSetStrokeColorWithColor(c, col);
657 }
658
659 static void SetStyleSpanRuns(ATSUTextLayout layout, SubRenderDiv *div)
660 {
661         unsigned span_count = [div->spans count];
662         int i;
663        
664         for (i = 0; i < span_count; i++) {
665                 SubRenderSpan *span = [div->spans objectAtIndex:i];
666                 UniCharArrayOffset next = (i == span_count-1) ? [div->text length] : ((SubRenderSpan*)[div->spans objectAtIndex:i+1])->offset;
667                 ATSUSetRunStyle(layout, span_ex(span)->style, span->offset, next - span->offset);
668         }
669 }
670
671 static Fixed RoundFixed(Fixed n) {return IntToFixed(FixedToInt(n));}
672
673 static void SetLayoutPositioning(ATSUTextLayout layout, Fixed lineWidth, UInt8 align)
674 {
675         const ATSUAttributeTag tags[] = {kATSULineFlushFactorTag, kATSULineWidthTag, kATSULineRotationTag};
676         const ByteCount          sizes[] = {sizeof(Fract), sizeof(ATSUTextMeasurement), sizeof(Fixed)};
677         Fract alignment;
678         Fixed fixzero = 0;
679         const ATSUAttributeValuePtr vals[] = {&alignment, &lineWidth, &fixzero};
680        
681         switch (align) {
682                 case kSubAlignmentLeft:
683                         alignment = FloatToFract(0);
684                         break;
685                 case kSubAlignmentCenter:
686                         alignment = kATSUCenterAlignment;
687                         break;
688                 case kSubAlignmentRight:
689                         alignment = fract1;
690                         break;
691         }
692        
693         ATSUSetLayoutControls(layout,sizeof(vals) / sizeof(ATSUAttributeValuePtr),tags,sizes,vals);
694 }
695
696 static UniCharArrayOffset BreakOneLineSpan(ATSUTextLayout layout, SubRenderDiv *div, unsigned char *breakOpportunities,
697                                                                                    ATSLayoutRecord *records, ItemCount lineLen, Fixed idealLineWidth, Fixed originalLineWidth, Fixed maximumLineWidth, unsigned numBreaks, unsigned lastHardBreak)
698 {               
699         int recOffset = 0;
700         Fixed widthOffset = 0;
701         BOOL foundABreak;
702         UniCharArrayOffset lastBreakOffset = 0;
703        
704         do {
705                 int j, lastIndex = 0;
706                 ATSUTextMeasurement error = 0;
707                 foundABreak = NO;
708                                
709                 for (j = recOffset; j < lineLen; j++) {
710                         ATSLayoutRecord *rec = &records[j];
711                         Fixed recPos = rec->realPos - widthOffset;
712                         UniCharArrayOffset charOffset = rec->originalOffset/2 + lastHardBreak;
713
714                         if (bitfield_test(breakOpportunities, charOffset)) {
715                                 if (recPos >= idealLineWidth) {
716                                         error = recPos - idealLineWidth;
717
718                                         if (lastIndex) {
719                                                 Fixed lastError = abs((records[lastIndex].realPos - widthOffset) - idealLineWidth);
720                                                 if (lastError < error || div->wrapStyle == kSubLineWrapBottomWider) {
721                                                         rec = &records[lastIndex];
722                                                         j = lastIndex;
723                                                         recPos = rec->realPos - widthOffset;
724                                                         charOffset = rec->originalOffset/2 + lastHardBreak;
725                                                 }
726                                         }
727                                        
728                                         // try not to leave short trailing lines
729                                         if ((recPos + (originalLineWidth - rec->realPos)) < maximumLineWidth) return 0;
730                                                
731                                         foundABreak = YES;
732                                         lastBreakOffset = charOffset;
733                                         ATSUSetSoftLineBreak(layout, charOffset);
734                                         break;
735                                 }
736                                
737                                 lastIndex = j;
738                         }
739                 }
740                
741                 widthOffset = records[j].realPos;
742                 recOffset = j;
743                 numBreaks--;
744         } while (foundABreak && numBreaks);
745                
746         return (numBreaks == 0) ? 0 : lastBreakOffset;
747 }
748
749 static void BreakLinesEvenly(ATSUTextLayout layout, SubRenderDiv *div, TextBreakLocatorRef breakLocator, Fixed breakingWidth, unichar *utext, unsigned textLen, ItemCount numHardBreaks)
750 {
751         UniCharArrayOffset hardBreaks[numHardBreaks+2];
752         declare_bitfield(breakOpportunities, textLen);
753         float fBreakingWidth = FixedToFloat(breakingWidth);
754         int i;
755        
756         ATSUGetSoftLineBreaks(layout, kATSUFromTextBeginning, kATSUToTextEnd, numHardBreaks, &hardBreaks[1], NULL);     
757         FindAllPossibleLineBreaks(breakLocator, utext, textLen, breakOpportunities);
758                
759         hardBreaks[0] = 0;
760         hardBreaks[numHardBreaks+1] = textLen;
761        
762         for (i = 0; i <= numHardBreaks; i++) {
763                 UniCharArrayOffset thisBreak = hardBreaks[i], nextBreak = hardBreaks[i+1];
764                 ATSUTextMeasurement leftEdge, rightEdge, ignore;
765                
766                 ATSUGetUnjustifiedBounds(layout, thisBreak, nextBreak - thisBreak, &leftEdge, &rightEdge, &ignore, &ignore);
767                 Fixed lineWidth = rightEdge - leftEdge;
768                 float fLineWidth = FixedToFloat(lineWidth);
769                                
770                 if (lineWidth > breakingWidth) {
771                         ATSLayoutRecord *records;
772                         ItemCount numRecords;
773                         unsigned idealSplitLines = ceil(fLineWidth / fBreakingWidth);
774                         Fixed idealBreakWidth = FloatToFixed(fLineWidth / idealSplitLines);
775                        
776                         ATSUDirectGetLayoutDataArrayPtrFromTextLayout(layout, thisBreak, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void*)&records, &numRecords);
777                                                
778                         UniCharArrayOffset res = BreakOneLineSpan(layout, div, breakOpportunities, records, numRecords, idealBreakWidth, lineWidth, breakingWidth, idealSplitLines-1, thisBreak);
779                        
780                         ATSUDirectReleaseLayoutDataArrayPtr(NULL, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void*)&records);
781                        
782                         if (res) ATSUBatchBreakLines(layout, res, nextBreak - res, breakingWidth, NULL);
783                 }
784         }
785 }
786
787 static UniCharArrayOffset *FindLineBreaks(ATSUTextLayout layout, SubRenderDiv *div, TextBreakLocatorRef breakLocator, ItemCount *nbreaks, Fixed breakingWidth, unichar *utext, unsigned textLen)
788 {
789         UniCharArrayOffset *breaks;
790         ItemCount breakCount=0;
791        
792         switch (div->wrapStyle) {
793                 case kSubLineWrapSimple:
794                         ATSUBatchBreakLines(layout, kATSUFromTextBeginning, kATSUToTextEnd, breakingWidth, &breakCount);
795                         break;
796                 case kSubLineWrapTopWider:
797                 case kSubLineWrapBottomWider:
798                 case kSubLineWrapNone:
799                         SetLayoutPositioning(layout, positiveInfinity, kSubAlignmentLeft);     
800                         ATSUBatchBreakLines(layout, kATSUFromTextBeginning, kATSUToTextEnd, positiveInfinity, &breakCount);
801                         if (div->wrapStyle != kSubLineWrapNone) {
802                                 BreakLinesEvenly(layout, div, breakLocator, breakingWidth, utext, textLen, breakCount);
803                                 ATSUGetSoftLineBreaks(layout, kATSUFromTextBeginning, kATSUToTextEnd, 0, NULL, &breakCount);
804                         }
805                         SetLayoutPositioning(layout, breakingWidth, div->alignH);       
806                         break;
807         }
808                
809         breaks = malloc(sizeof(UniCharArrayOffset) * (breakCount+2));
810         ATSUGetSoftLineBreaks(layout, kATSUFromTextBeginning, kATSUToTextEnd, breakCount, &breaks[1], NULL);
811        
812         breaks[0] = 0;
813         breaks[breakCount+1] = textLen;
814                
815         *nbreaks = breakCount;
816         return breaks;
817 }
818
819 typedef struct {
820         UniCharArrayOffset *breaks;
821         ItemCount breakCount;
822         unsigned lStart, lEnd;
823         SInt8 direction;
824 } BreakContext;
825
826 enum {kTextLayerShadow, kTextLayerOutline, kTextLayerPrimary};
827
828 static BOOL SetupCGForSpan(CGContextRef c, SubATSUISpanEx *spanEx, SubATSUISpanEx *lastSpanEx, SubRenderDiv *div, int textType, BOOL endLayer)
829 {       
830 #define if_different(x) if (!lastSpanEx || lastSpanEx-> x != spanEx-> x)
831        
832         switch (textType) {
833                 case kTextLayerShadow:
834                         if_different(shadowColor) {
835                                 if (endLayer) CGContextEndTransparencyLayer(c);
836
837                                 SetColor(c, fillc, spanEx->shadowColor);
838                                 SetColor(c, strokec, spanEx->shadowColor);
839                                 if (CGColorGetAlpha(spanEx->shadowColor) != 1.) {
840                                         endLayer = YES;
841                                         CGContextBeginTransparencyLayer(c, NULL);
842                                 } else endLayer = NO;
843                         }
844                         break;
845                        
846                 case kTextLayerOutline:
847                         if_different(outlineRadius) CGContextSetLineWidth(c, spanEx->outlineRadius ? (spanEx->outlineRadius*2. + .5) : 0.);
848                         if_different(outlineColor)  SetColor(c, (div->styleLine->borderStyle == kSubBorderStyleNormal) ? strokec : fillc, spanEx->outlineColor);
849                        
850                         if_different(outlineAlpha) {
851                                 if (endLayer) CGContextEndTransparencyLayer(c);
852                                
853                                 CGContextSetAlpha(c, spanEx->outlineAlpha);
854                                 if (spanEx->outlineAlpha != 1.) {
855                                         endLayer = YES;
856                                         CGContextBeginTransparencyLayer(c, NULL);
857                                 } else endLayer = NO;
858                         }
859                                
860                         break;
861                 case kTextLayerPrimary:
862                         if_different(primaryColor) SetColor(c, fillc, spanEx->primaryColor);
863                        
864                         if_different(primaryAlpha) {
865                                 if (endLayer) CGContextEndTransparencyLayer(c);
866
867                                 CGContextSetAlpha(c, spanEx->primaryAlpha);
868                                 if (spanEx->primaryAlpha != 1.) {
869                                         endLayer = YES;
870                                         CGContextBeginTransparencyLayer(c, NULL);
871                                 } else endLayer = NO;
872                         }
873                         break;
874         }
875        
876         return endLayer;
877 }
878
879 static void RenderActualLine(ATSUTextLayout layout, UniCharArrayOffset thisBreak, UniCharArrayOffset lineLen, Fixed penX, Fixed penY, CGContextRef c, SubRenderDiv *div, SubATSUISpanEx *spanEx, int textType)
880 {       
881         if (textType == kTextLayerOutline && div->styleLine->borderStyle == kSubBorderStyleBox) {
882                 ATSUTextMeasurement lineWidth, lineHeight, lineX, lineY;
883                 UniCharArrayOffset breaks[2] = {thisBreak, thisBreak + lineLen};
884                 GetTypographicRectangleForLayout(layout, breaks, 0, FloatToFixed(spanEx->outlineRadius), &lineX, &lineY, &lineHeight, &lineWidth);
885                
886                 CGRect borderRect = CGRectMake(FixedToFloat(lineX + penX), FixedToFloat(penY - lineY), FixedToFloat(lineWidth), FixedToFloat(lineHeight));
887                
888                 ExpandCGRect(&borderRect, spanEx->outlineRadius);
889                
890                 borderRect.origin.x = floor(borderRect.origin.x);
891                 borderRect.origin.y = floor(borderRect.origin.y);
892                 borderRect.size.width = ceil(borderRect.size.width);
893                 borderRect.size.height = ceil(borderRect.size.height);
894                
895                 CGContextFillRect(c, borderRect);
896         } else ATSUDrawText(layout, thisBreak, lineLen, RoundFixed(penX), RoundFixed(penY));
897 }
898
899 static Fixed DrawTextLines(CGContextRef c, ATSUTextLayout layout, SubRenderDiv *div, const BreakContext breakc, Fixed penX, Fixed penY, SubATSUISpanEx *firstSpanEx, int textType)
900 {
901         int i;
902         BOOL endLayer = NO;
903         SubATSUISpanEx *lastSpanEx = nil;
904         const CGTextDrawingMode textModes[] = {kCGTextFillStroke, kCGTextStroke, kCGTextFill};
905                
906         CGContextSetTextDrawingMode(c, textModes[textType]);
907        
908         if (!(div->render_complexity & renderMultipleParts)) endLayer = SetupCGForSpan(c, firstSpanEx, lastSpanEx, div, textType, endLayer);
909        
910         for (i = breakc.lStart; i != breakc.lEnd; i -= breakc.direction) {
911                 UniCharArrayOffset thisBreak = breakc.breaks[i], nextBreak = breakc.breaks[i+1], linelen = nextBreak - thisBreak;
912                 float extraHeight = 0;
913                
914                 if (!(div->render_complexity & renderMultipleParts)) {
915                         RenderActualLine(layout, thisBreak, linelen, penX, penY, c, div, firstSpanEx, textType);
916                         extraHeight = div->styleLine->outlineRadius;
917                 } else {
918                         int j, spans = [div->spans count];
919                         for (j = 0; j < spans; j++) {
920                                 SubRenderSpan *span = [div->spans objectAtIndex:j];
921                                 SubATSUISpanEx *spanEx = span->ex;
922                                 UniCharArrayOffset spanLen, drawStart, drawLen;
923