source: trunk/Subtitles/SubATSUIRenderer.m @ 1036

Revision 1036, 43.8 KB checked in by astrange, 5 years ago (diff)

Build system/logging:

  • Don't regenerate SubParsing?.m/ffmpeg universal libs if not needed
  • Print "Perian" instead of "Perian Codec"

Subtitles:

  • Rewrite SubSerializer?; much shorter and correct now (aside from line order).

(fixes many dropped lines in Live-eviL Captain Harlock and gg Sayonara Zetsubou Sensei)

  • Fix some bugs where line spans were rendered more than once with the wrong style.

(fixes transparency in Spice and Wolf 01 [ADTRW].mkv)

  • Ignore \blur properly

(fixes unstyled lines in [gg]_Goku_Sayonara_Zetsubou_Sensei_-_02_[D284BF24].mkv)

  • Fix an overrelease crash
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
15static float GetWinFontSizeScale(ATSFontRef font);
16static 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, platformSizeScale, fontSize;
27        BOOL blurEdges;
28        ATSUFontID font;
29        ATSUVerticalCharacterType fontVertical;
30}
31-(SubATSUISpanEx*)initWithStyle:(ATSUStyle)style_ subStyle:(SubStyle*)sstyle colorSpace:(CGColorSpaceRef)cs;
32-(SubATSUISpanEx*)clone;
33@end
34
35@implementation SubATSUISpanEx
36-(void)dealloc
37{
38        ATSUDisposeStyle(style);
39        CGColorRelease(primaryColor);
40        CGColorRelease(outlineColor);
41        CGColorRelease(shadowColor);
42        [super dealloc];
43}
44
45-(void)finalize
46{
47        ATSUDisposeStyle(style);
48        CGColorRelease(primaryColor);
49        CGColorRelease(outlineColor);
50        CGColorRelease(shadowColor);
51        [super finalize];
52}
53
54static CGColorRef MakeCGColorFromRGBA(SubRGBAColor c, CGColorSpaceRef cspace)
55{
56        const float components[] = {c.red, c.green, c.blue, c.alpha};
57       
58        return CGColorCreate(cspace, components);
59}
60
61static CGColorRef MakeCGColorFromRGBOpaque(SubRGBAColor c, CGColorSpaceRef cspace)
62{
63        SubRGBAColor c2 = c;
64        c2.alpha = 1;
65        return MakeCGColorFromRGBA(c2, cspace);
66}
67
68static CGColorRef CloneCGColorWithAlpha(CGColorRef c, float alpha)
69{
70        CGColorRef new = CGColorCreateCopyWithAlpha(c, alpha);
71        CGColorRelease(c);
72        return new;
73}
74
75-(SubATSUISpanEx*)initWithStyle:(ATSUStyle)style_ subStyle:(SubStyle*)sstyle colorSpace:(CGColorSpaceRef)cs
76{
77        ByteCount unused;
78       
79        if (self = [super init]) {
80                ATSUCreateAndCopyStyle(style_, &style);
81               
82                primaryColor = MakeCGColorFromRGBOpaque(sstyle->primaryColor, cs);
83                primaryAlpha = sstyle->primaryColor.alpha;
84                outlineColor = MakeCGColorFromRGBOpaque(sstyle->outlineColor, cs);
85                outlineAlpha = sstyle->outlineColor.alpha;
86                shadowColor  = MakeCGColorFromRGBA(sstyle->shadowColor,  cs);
87                outlineRadius = sstyle->outlineRadius;
88                shadowDist = sstyle->shadowDist;
89                scaleX = sstyle->scaleX / 100.;
90                scaleY = sstyle->scaleY / 100.;
91                angle = sstyle->angle;
92                platformSizeScale = sstyle->platformSizeScale;
93                fontSize = sstyle->size;
94                ATSUGetAttribute(style, kATSUFontTag, sizeof(ATSUFontID), &font, &unused);
95                ATSUGetAttribute(style, kATSUVerticalCharacterTag, sizeof(ATSUVerticalCharacterType), &fontVertical, &unused);
96        }
97       
98        return self;
99}
100
101-(SubATSUISpanEx*)clone
102{
103        SubATSUISpanEx *ret = [[SubATSUISpanEx alloc] init];
104       
105        ATSUCreateAndCopyStyle(style, &ret->style);
106        ret->primaryColor = CGColorRetain(primaryColor);
107        ret->primaryAlpha = primaryAlpha;
108        ret->outlineColor = CGColorRetain(outlineColor);
109        ret->outlineAlpha = outlineAlpha;
110        ret->shadowColor = CGColorRetain(shadowColor);
111        ret->outlineRadius = outlineRadius;
112        ret->shadowDist = shadowDist;
113        ret->scaleX = scaleX;
114        ret->scaleY = scaleY;
115        ret->angle = angle;
116        ret->platformSizeScale = platformSizeScale;
117        ret->fontSize = fontSize;
118        ret->font = font;
119        ret->fontVertical = fontVertical;
120       
121        return ret;
122}
123
124-(NSString*)description
125{
126        return [NSString stringWithFormat:@"SpanEx with alpha %f/%f", primaryAlpha, outlineAlpha];
127}
128@end
129
130#define span_ex(span) ((SubATSUISpanEx*)span->ex)
131
132static void SetATSUStyleFlag(ATSUStyle style, ATSUAttributeTag t, Boolean v)
133{
134        const ATSUAttributeTag tags[] = {t};
135        const ByteCount          sizes[] = {sizeof(v)};
136        const ATSUAttributeValuePtr vals[] = {&v};
137       
138        ATSUSetAttributes(style,1,tags,sizes,vals);
139}
140
141static void SetATSUStyleOther(ATSUStyle style, ATSUAttributeTag t, ByteCount s, const ATSUAttributeValuePtr v)
142{
143        const ATSUAttributeTag tags[] = {t};
144        const ByteCount          sizes[] = {s};
145        const ATSUAttributeValuePtr vals[] = {v};
146       
147        ATSUSetAttributes(style,1,tags,sizes,vals);
148}
149
150static void SetATSULayoutOther(ATSUTextLayout l, ATSUAttributeTag t, ByteCount s, const ATSUAttributeValuePtr v)
151{
152        const ATSUAttributeTag tags[] = {t};
153        const ByteCount          sizes[] = {s};
154        const ATSUAttributeValuePtr vals[] = {v};
155       
156        ATSUSetLayoutControls(l,1,tags,sizes,vals);
157}
158
159@implementation SubATSUIRenderer
160
161// from Apple Q&A 1396
162static CGColorSpaceRef CreateICCColorSpaceFromPathToProfile (const char * iccProfilePath) {
163        CMProfileRef    iccProfile = NULL;
164        CGColorSpaceRef iccColorSpace = NULL;
165        CMProfileLocation loc;
166       
167        // Specify that the location of the profile will be a POSIX path to the profile.
168        loc.locType = cmPathBasedProfile;
169       
170        // Make sure the path is not larger then the buffer
171        if(strlen(iccProfilePath) > sizeof(loc.u.pathLoc.path))
172                return NULL;
173       
174        // Copy the path the profile into the CMProfileLocation structure
175        strcpy (loc.u.pathLoc.path, iccProfilePath);
176       
177        // Open the profile
178        if (CMOpenProfile(&iccProfile, &loc) != noErr)
179        {
180                iccProfile = (CMProfileRef) 0;
181                return NULL;
182        }
183       
184        // Create the ColorSpace with the open profile.
185        iccColorSpace = CGColorSpaceCreateWithPlatformColorSpace( iccProfile );
186       
187        // Close the profile now that we have what we need from it.
188        CMCloseProfile(iccProfile);
189       
190        return iccColorSpace;
191}
192
193static CGColorSpaceRef CreateColorSpaceFromSystemICCProfileName(CFStringRef profileName) {
194        FSRef pathToProfilesFolder;
195    FSRef pathToProfile;
196       
197        // Find the Systems Color Sync Profiles folder
198        if(FSFindFolder(kOnSystemDisk, kColorSyncProfilesFolderType,
199                                        kDontCreateFolder, &pathToProfilesFolder) == noErr) {
200               
201                // Make a UniChar string of the profile name
202                UniChar uniBuffer[sizeof(CMPathLocation)];
203                CFStringGetCharacters (profileName,CFRangeMake(0,CFStringGetLength(profileName)),uniBuffer);
204               
205                // Create a FSRef to the profile in the Systems Color Sync Profile folder
206                if(FSMakeFSRefUnicode (&pathToProfilesFolder,CFStringGetLength(profileName),uniBuffer,
207                                                           kUnicodeUTF8Format,&pathToProfile) == noErr) {
208                        unsigned char path[sizeof(CMPathLocation)];
209                       
210                        // Write the posix path to the profile into our path buffer from the FSRef
211                        if(FSRefMakePath (&pathToProfile,path,sizeof(CMPathLocation)) == noErr)
212                                return CreateICCColorSpaceFromPathToProfile((char*)path);
213                }
214        }
215       
216        return NULL;
217}
218
219static CGColorSpaceRef CreateICCsRGBColorSpace() {
220        return CreateColorSpaceFromSystemICCProfileName(CFSTR("sRGB Profile.icc"));
221}
222
223static CGColorSpaceRef GetSRGBColorSpace() {
224        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
225        CGColorSpaceRef cs = CreateICCsRGBColorSpace();
226        [pool release];
227        return cs;
228}
229
230-(SubATSUIRenderer*)initWithVideoWidth:(float)width videoHeight:(float)height;
231{
232        if (self = [super init]) {
233                ATSUCreateTextLayout(&layout);
234                ubuffer = malloc(sizeof(unichar) * 128);
235                breakbuffer = malloc(sizeof(UniCharArrayOffset) * 2);
236               
237                srgbCSpace = GetSRGBColorSpace();
238               
239                videoWidth = width;
240                videoHeight = height;
241               
242                UCCreateTextBreakLocator(NULL, 0, kUCTextBreakLineMask, &breakLocator);
243                [[SubContext alloc] initWithNonSSAType:kSubTypeSRT delegate:self];
244        }
245       
246        return self;
247}
248
249-(SubATSUIRenderer*)initWithSSAHeader:(NSString*)header videoWidth:(float)width videoHeight:(float)height;
250{
251        if (self = [super init]) {
252                unsigned hlength = [header length];
253                unichar *uheader = malloc(sizeof(unichar) * hlength);
254               
255                header = STStandardizeStringNewlines(header);
256                [header getCharacters:uheader];
257               
258                NSDictionary *headers;
259                NSArray *styles;
260                SubParseSSAFile(uheader, hlength, &headers, &styles, NULL);
261                free(uheader);
262               
263                ubuffer = malloc(sizeof(unichar) * 128);
264                breakbuffer = malloc(sizeof(UniCharArrayOffset) * 2);
265               
266                videoWidth = width;
267                videoHeight = height;
268               
269                ATSUCreateTextLayout(&layout);
270                srgbCSpace = GetSRGBColorSpace();
271               
272                UCCreateTextBreakLocator(NULL, 0, kUCTextBreakLineMask, &breakLocator);
273                [[SubContext alloc] initWithHeaders:headers styles:styles extraData:header delegate:self];
274        }
275       
276        return self;
277}
278
279-(void)dealloc
280{
281        [context release];
282        free(breakbuffer);
283        free(ubuffer);
284        CGColorSpaceRelease(srgbCSpace);
285        UCDisposeTextBreakLocator(&breakLocator);
286        ATSUDisposeTextLayout(layout);
287        [super dealloc];
288}
289
290-(void)finalize
291{
292        free(breakbuffer);
293        free(ubuffer);
294        UCDisposeTextBreakLocator(&breakLocator);
295        ATSUDisposeTextLayout(layout);
296        [super finalize];
297}
298
299-(void)completedHeaderParsing:(SubContext*)sc
300{
301        context = sc;
302        screenScaleX = videoWidth / context->resX;
303        screenScaleY = videoHeight / context->resY;
304}
305
306-(float)aspectRatio
307{
308        return videoWidth / videoHeight;
309}
310
311static NSMutableDictionary *fontIDCache = nil;
312static ATSUFontID fontCount=-1, *fontIDs = NULL;
313
314static void CleanupFontIDCache() __attribute__((destructor));
315static void CleanupFontIDCache()
316{
317        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
318        if (fontIDCache) [fontIDCache release];
319        if (fontIDs) free(fontIDs);
320        fontIDCache = nil;
321        fontIDs = NULL;
322        [pool release];
323}
324
325// XXX: Assumes ATSUFontID = ATSFontRef. This is true.
326static ATSUFontID GetFontIDForSSAName(NSString *name)
327{               
328        if (fontIDCache) {
329                NSNumber *idN = [fontIDCache objectForKey:[name lowercaseString]];
330               
331                if (idN) return [idN intValue];
332        } else
333                fontIDCache = [[NSMutableDictionary alloc] init];
334       
335        ByteCount nlen = [name length];
336        unichar *uname = (unichar*)[name cStringUsingEncoding:NSUnicodeStringEncoding];
337        ATSUFontID font;
338       
339        // should be kFontMicrosoftPlatform for the platform code
340        // but isn't for bug workarounds
341        ATSUFindFontFromName(uname, nlen * sizeof(unichar), kFontFamilyName, kFontNoPlatformCode, kFontNoScript, kFontNoLanguage, &font);
342       
343        if (font == kATSUInvalidFontID) font = ATSFontFindFromName((CFStringRef)name, kATSOptionFlagsDefault); // for bugs in ATS under 10.4
344        if (font == kATSUInvalidFontID) { // try a case-insensitive search
345                if (fontCount == -1) {
346                        ATSUFontCount(&fontCount);
347                        fontIDs = malloc(sizeof(ATSUFontID[fontCount]));
348                        ATSUGetFontIDs(fontIDs, fontCount, &fontCount);
349                }
350                               
351                ByteCount len;
352                ItemCount x, index;
353                const ByteCount kBufLength = 1024/sizeof(unichar);
354                unichar buf[kBufLength];
355         
356                for (x = 0; x < fontCount; x++) {
357                        ATSUFindFontName(fontIDs[x], kFontFamilyName, kFontMicrosoftPlatform, kFontNoScript, kFontNoLanguage, kBufLength, (Ptr)buf, &len, &index);
358                        NSString *fname = [NSString stringWithCharacters:buf length:len/sizeof(unichar)];
359                       
360                        if ([name caseInsensitiveCompare:fname] == NSOrderedSame) {
361                                font = fontIDs[x];
362                                break;
363                        }
364                }
365               
366                if (font == kATSUInvalidFontID) font = ATSFontFindFromName((CFStringRef)@"Helvetica",kATSOptionFlagsDefault); // final fallback
367        }
368       
369        [fontIDCache setValue:[NSNumber numberWithInt:font] forKey:[name lowercaseString]];
370         
371        return font;
372}
373
374-(void*)completedStyleParsing:(SubStyle*)s
375{
376        const ATSUAttributeTag tags[] = {kATSUStyleRenderingOptionsTag, kATSUSizeTag, kATSUQDBoldfaceTag, kATSUQDItalicTag, kATSUQDUnderlineTag, kATSUStyleStrikeThroughTag, kATSUFontTag, kATSUVerticalCharacterTag};
377        const ByteCount          sizes[] = {sizeof(ATSStyleRenderingOptions), sizeof(Fixed), sizeof(Boolean), sizeof(Boolean), sizeof(Boolean), sizeof(Boolean), sizeof(ATSUFontID), sizeof(ATSUVerticalCharacterType)};
378       
379        NSString *fn = s->fontname;
380        ATSUVerticalCharacterType vertical = ParseFontVerticality(&fn) ? kATSUStronglyVertical : kATSUStronglyHorizontal;
381        ATSUFontID font = GetFontIDForSSAName(fn);
382        ATSFontRef fontRef = font;
383        ATSStyleRenderingOptions opt = kATSStyleApplyAntiAliasing;
384        Fixed size;
385        Boolean b = s->weight > 0, i = s->italic, u = s->underline, st = s->strikeout;
386        ATSUStyle style;
387               
388        const ATSUAttributeValuePtr vals[] = {&opt, &size, &b, &i, &u, &st, &font, &vertical};
389       
390        if (!s->platformSizeScale) s->platformSizeScale = GetWinFontSizeScale(fontRef);
391        size = FloatToFixed(s->size * s->platformSizeScale * screenScaleY); //FIXME several other values also change relative to PlayRes but aren't handled
392       
393        ATSUCreateStyle(&style);
394        ATSUSetAttributes(style, sizeof(tags) / sizeof(ATSUAttributeTag), tags, sizes, vals);
395       
396        if (s->tracking > 0) { // bug in VSFilter: negative tracking in style lines is ignored
397                Fixed tracking = FloatToFixed(s->tracking);
398               
399                SetATSUStyleOther(style, kATSUAfterWithStreamShiftTag, sizeof(Fixed), &tracking);
400        }
401       
402        if (s->scaleX != 100. || s->scaleY != 100.) {
403                CGAffineTransform mat = CGAffineTransformMakeScale(s->scaleX / 100., s->scaleY / 100.);
404               
405                SetATSUStyleOther(style, kATSUFontMatrixTag, sizeof(CGAffineTransform), &mat);
406        }
407
408        const ATSUFontFeatureType ftypes[] = {kLigaturesType, kTypographicExtrasType, kTypographicExtrasType, kTypographicExtrasType};
409        const ATSUFontFeatureSelector fsels[] = {kCommonLigaturesOnSelector, kSmartQuotesOnSelector, kPeriodsToEllipsisOnSelector, kHyphenToEnDashOnSelector};
410       
411        ATSUSetFontFeatures(style, sizeof(ftypes) / sizeof(ATSUFontFeatureType), ftypes, fsels);
412
413        return style;
414}
415
416-(void)releaseStyleExtra:(void*)ex
417{
418        ATSUDisposeStyle(ex);
419}
420
421-(void*)spanExtraFromRenderDiv:(SubRenderDiv*)div
422{
423        return [[SubATSUISpanEx alloc] initWithStyle:(ATSUStyle)div->styleLine->ex subStyle:div->styleLine colorSpace:srgbCSpace];
424}
425
426-(void*)cloneSpanExtra:(SubRenderSpan*)span
427{
428        return [(SubATSUISpanEx*)span->ex clone];
429}
430
431-(void)releaseSpanExtra:(void*)ex
432{
433        SubATSUISpanEx *spanEx = ex;
434        [spanEx release];
435}
436
437-(NSString*)describeSpanEx:(void*)ex
438{
439        SubATSUISpanEx *spanEx = ex;
440        return [spanEx description];
441}
442
443static void UpdateFontNameSize(SubATSUISpanEx *spanEx, float screenScale)
444{
445        Fixed fSize = FloatToFixed(spanEx->fontSize * spanEx->platformSizeScale * screenScale);
446        SetATSUStyleOther(spanEx->style, kATSUFontTag, sizeof(ATSUFontID), &spanEx->font);
447        SetATSUStyleOther(spanEx->style, kATSUVerticalCharacterTag, sizeof(ATSUVerticalCharacterType), &spanEx->fontVertical);
448        SetATSUStyleOther(spanEx->style, kATSUSizeTag, sizeof(Fixed), &fSize);
449}
450
451enum {renderMultipleParts = 1, // call ATSUDrawText more than once, needed for color/border changes in the middle of lines
452          renderManualShadows = 2, // CG shadows can't change inside a line... probably
453          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
454
455-(void)spanChangedTag:(SSATagType)tag span:(SubRenderSpan*)span div:(SubRenderDiv*)div param:(void*)p
456{
457        SubATSUISpanEx *spanEx = span->ex;
458        BOOL isFirstSpan = [div->spans count] == 0;
459        Boolean bval;
460        int ival;
461        float fval;
462        NSString *sval;
463        Fixed fixval;
464        CGColorRef color;
465        CGAffineTransform mat;
466       
467#define bv() bval = *(int*)p;
468#define iv() ival = *(int*)p;
469#define fv() fval = *(float*)p;
470#define sv() sval = *(NSString**)p;
471#define fixv() fv(); fixval = FloatToFixed(fval);
472#define colorv() color = MakeCGColorFromRGBA(ParseSSAColor(*(int*)p), srgbCSpace);
473       
474        switch (tag) {
475                case tag_b:
476                        bv(); // XXX font weight variations
477                        SetATSUStyleFlag(spanEx->style, kATSUQDBoldfaceTag, bval != 0);
478                        break;
479                case tag_i:
480                        bv();
481                        SetATSUStyleFlag(spanEx->style, kATSUQDItalicTag, bval);
482                        break;
483                case tag_u:
484                        bv();
485                        SetATSUStyleFlag(spanEx->style, kATSUQDUnderlineTag, bval);
486                        break;
487                case tag_s:
488                        bv();
489                        SetATSUStyleFlag(spanEx->style, kATSUStyleStrikeThroughTag, bval);
490                        break;
491                case tag_bord:
492                        fv();
493                        if (!isFirstSpan) div->render_complexity |= renderMultipleParts;
494                        spanEx->outlineRadius = fval;
495                        break;
496                case tag_shad:
497                        fv();
498                        if (!isFirstSpan) div->render_complexity |= renderMultipleParts | renderManualShadows;
499                        spanEx->shadowDist = fval;
500                        break;
501                case tag_fn:
502                        sv();
503                        spanEx->fontVertical = ParseFontVerticality(&sval) ? kATSUStronglyVertical : kATSUStronglyHorizontal;
504                        spanEx->font = GetFontIDForSSAName(sval);
505                        UpdateFontNameSize(spanEx, screenScaleY);
506                        break;
507                case tag_fs:
508                        fv();
509                        spanEx->fontSize = fval;
510                        UpdateFontNameSize(spanEx, screenScaleY);
511                        break;
512                case tag_1c:
513                        CGColorRelease(spanEx->primaryColor);
514                        if (!isFirstSpan) div->render_complexity |= renderMultipleParts;
515                        colorv();
516                        spanEx->primaryColor = color;
517                        break;
518                case tag_3c:
519                        CGColorRelease(spanEx->outlineColor);
520                        if (!isFirstSpan) div->render_complexity |= renderMultipleParts;
521                        {
522                                SubRGBAColor rgba = ParseSSAColor(*(int*)p);
523                                spanEx->outlineColor = MakeCGColorFromRGBOpaque(rgba, srgbCSpace);
524                                spanEx->outlineAlpha = rgba.alpha;
525                        }
526                        break;
527                case tag_4c:
528                        CGColorRelease(spanEx->shadowColor);
529                        if (!isFirstSpan) div->render_complexity |= renderMultipleParts | renderManualShadows;
530                        colorv();
531                        spanEx->shadowColor = color;
532                        break;
533                case tag_fscx:
534                        fv();
535                        spanEx->scaleX = fval / 100.;
536                        mat = CGAffineTransformMakeScale(spanEx->scaleX, spanEx->scaleY);
537                        SetATSUStyleOther(spanEx->style, kATSUFontMatrixTag, sizeof(CGAffineTransform), &mat);
538                        break;
539                case tag_fscy:
540                        fv();
541                        spanEx->scaleY = fval / 100.;
542                        mat = CGAffineTransformMakeScale(spanEx->scaleX, spanEx->scaleY);
543                        SetATSUStyleOther(spanEx->style, kATSUFontMatrixTag, sizeof(CGAffineTransform), &mat);
544                        break;
545                case tag_fsp:
546                        fixv();
547                        SetATSUStyleOther(spanEx->style, kATSUAfterWithStreamShiftTag, sizeof(Fixed), &fixval);
548                        break;
549                case tag_frz:
550                        fv();
551                        if (!isFirstSpan) div->render_complexity |= renderComplexTransforms; // this one's hard
552                        spanEx->angle = fval;
553                        break;
554                case tag_1a:
555                        iv();
556                        if (!isFirstSpan) div->render_complexity |= renderMultipleParts;
557                        spanEx->primaryAlpha = (255-ival)/255.;
558                        break;
559                case tag_3a:
560                        iv();
561                        if (!isFirstSpan) div->render_complexity |= renderMultipleParts;
562                        spanEx->outlineAlpha = (255-ival)/255.;
563                        break;
564                case tag_4a:
565                        iv();
566                        if (!isFirstSpan) div->render_complexity |= renderMultipleParts | renderManualShadows;
567                        spanEx->shadowColor = CloneCGColorWithAlpha(spanEx->shadowColor, (255-ival)/255.);
568                        break;
569                case tag_alpha:
570                        iv();
571                        fval = (255-ival)/255.;
572                        if (!isFirstSpan) div->render_complexity |= renderMultipleParts | renderManualShadows;
573                        spanEx->primaryAlpha = spanEx->outlineAlpha = fval;
574                        spanEx->shadowColor = CloneCGColorWithAlpha(spanEx->shadowColor, fval);
575                        break;
576                case tag_r:
577                        sv();
578                        if (!isFirstSpan) div->render_complexity |= renderMultipleParts | renderManualShadows;
579                        {
580                                SubStyle *style = [context->styles objectForKey:sval];
581                                if (!style) style = div->styleLine;
582                               
583                                [spanEx release];
584                                span->ex = [[SubATSUISpanEx alloc] initWithStyle:(ATSUStyle)style->ex subStyle:style colorSpace:srgbCSpace];
585                        }
586                        break;
587                case tag_be:
588                        bv();
589                        if (!isFirstSpan) div->render_complexity |= renderMultipleParts; // XXX blur edges
590                        spanEx->blurEdges = bval;
591                        break;
592                default:
593                        Codecprintf(NULL, "Unimplemented SSA tag #%d\n",tag);
594        }
595}
596
597#pragma mark Rendering Helper Functions
598
599static ATSUTextMeasurement GetLineHeight(ATSUTextLayout layout, UniCharArrayOffset lpos)
600{
601        ATSUTextMeasurement ascent, descent;
602       
603        ATSUGetLineControl(layout, lpos, kATSULineAscentTag,  sizeof(ATSUTextMeasurement), &ascent,  NULL);
604        ATSUGetLineControl(layout, lpos, kATSULineDescentTag, sizeof(ATSUTextMeasurement), &descent, NULL);
605       
606        return ascent + descent;
607}
608
609static void ExpandCGRect(CGRect *rect, float radius)
610{
611        rect->origin.x -= radius;
612        rect->origin.y -= radius;
613        rect->size.height += radius*2.;
614        rect->size.width += radius*2.;
615}
616
617static void GetTypographicRectangleForLayout(ATSUTextLayout layout, UniCharArrayOffset *breaks, ItemCount breakCount, Fixed extraHeight, Fixed *lX, Fixed *lY, Fixed *height, Fixed *width)
618{
619        ATSTrapezoid trap = {0};
620        ItemCount trapCount;
621        FixedRect largeRect = {0};
622        Fixed baseY = 0;
623        int i;
624
625        for (i = breakCount; i >= 0; i--) {             
626                UniCharArrayOffset end = breaks[i+1];
627                FixedRect rect;
628               
629                ATSUGetGlyphBounds(layout, 0, baseY, breaks[i], end-breaks[i], kATSUseDeviceOrigins, 1, &trap, &trapCount);
630
631                baseY += GetLineHeight(layout, breaks[i]) + extraHeight;
632               
633                rect.bottom = MAX(trap.lowerLeft.y, trap.lowerRight.y);
634                rect.left = MIN(trap.lowerLeft.x, trap.upperLeft.x);
635                rect.top = MIN(trap.upperLeft.y, trap.upperRight.y);
636                rect.right = MAX(trap.lowerRight.x, trap.upperRight.x);
637               
638                if (i == breakCount) largeRect = rect;
639               
640                largeRect.bottom = MAX(largeRect.bottom, rect.bottom);
641                largeRect.left = MIN(largeRect.left, rect.left);
642                largeRect.top = MIN(largeRect.top, rect.top);
643                largeRect.right = MAX(largeRect.right, rect.right);
644        }
645       
646        if (lX) *lX = largeRect.left;
647        if (lY) *lY = largeRect.bottom;
648        *height = largeRect.bottom - largeRect.top;
649        *width = largeRect.right - largeRect.left;
650}
651
652#if 0
653static void GetImageBoundingBoxForLayout(ATSUTextLayout layout, UniCharArrayOffset *breaks, ItemCount breakCount, Fixed extraHeight, Fixed *lX, Fixed *lY, Fixed *height, Fixed *width)
654{
655        Rect largeRect = {0};
656        ATSUTextMeasurement baseY = 0;
657        int i;
658       
659        for (i = breakCount; i >= 0; i--) {
660                UniCharArrayOffset end = breaks[i+1];
661                Rect rect;
662               
663                ATSUMeasureTextImage(layout, breaks[i], end-breaks[i], 0, baseY, &rect);
664               
665                baseY += GetLineHeight(layout, breaks[i]) + extraHeight;
666               
667                if (i == breakCount) largeRect = rect;
668               
669                largeRect.bottom = MAX(largeRect.bottom, rect.bottom);
670                largeRect.left = MIN(largeRect.left, rect.left);
671                largeRect.top = MIN(largeRect.top, rect.top);
672                largeRect.right = MAX(largeRect.right, rect.right);
673                        }
674       
675       
676        if (lX) *lX = IntToFixed(largeRect.left);
677        if (lY) *lY = IntToFixed(largeRect.bottom);
678        *height = IntToFixed(largeRect.bottom - largeRect.top);
679        *width = IntToFixed(largeRect.right - largeRect.left);
680}
681#endif
682
683enum {fillc, strokec};
684
685static void SetColor(CGContextRef c, int whichcolor, CGColorRef col)
686{
687        if (whichcolor == fillc) CGContextSetFillColorWithColor(c, col);
688        else CGContextSetStrokeColorWithColor(c, col);
689}
690
691static void SetStyleSpanRuns(ATSUTextLayout layout, SubRenderDiv *div)
692{
693        unsigned span_count = [div->spans count];
694        int i;
695       
696        for (i = 0; i < span_count; i++) {
697                SubRenderSpan *span = [div->spans objectAtIndex:i];
698                UniCharArrayOffset next = (i == span_count-1) ? [div->text length] : ((SubRenderSpan*)[div->spans objectAtIndex:i+1])->offset;
699                ATSUSetRunStyle(layout, span_ex(span)->style, span->offset, next - span->offset);
700        }
701}
702
703static Fixed RoundFixed(Fixed n) {return IntToFixed(FixedToInt(n));}
704
705static void SetLayoutPositioning(ATSUTextLayout layout, Fixed lineWidth, UInt8 align)
706{
707        const ATSUAttributeTag tags[] = {kATSULineFlushFactorTag, kATSULineWidthTag, kATSULineRotationTag};
708        const ByteCount          sizes[] = {sizeof(Fract), sizeof(ATSUTextMeasurement), sizeof(Fixed)};
709        Fract alignment;
710        Fixed fixzero = 0;
711        const ATSUAttributeValuePtr vals[] = {&alignment, &lineWidth, &fixzero};
712       
713        switch (align) {
714                case kSubAlignmentLeft:
715                        alignment = FloatToFract(0);
716                        break;
717                case kSubAlignmentCenter:
718                        alignment = kATSUCenterAlignment;
719                        break;
720                case kSubAlignmentRight:
721                        alignment = fract1;
722                        break;
723        }
724       
725        ATSUSetLayoutControls(layout,sizeof(vals) / sizeof(ATSUAttributeValuePtr),tags,sizes,vals);
726}
727
728static UniCharArrayOffset BreakOneLineSpan(ATSUTextLayout layout, SubRenderDiv *div, unsigned char *breakOpportunities,
729                                                                                   ATSLayoutRecord *records, ItemCount lineLen, Fixed idealLineWidth, Fixed originalLineWidth, Fixed maximumLineWidth, unsigned numBreaks, unsigned lastHardBreak)
730{               
731        int recOffset = 0;
732        Fixed widthOffset = 0;
733        BOOL foundABreak;
734        UniCharArrayOffset lastBreakOffset = 0;
735       
736        do {
737                int j, lastIndex = 0;
738                ATSUTextMeasurement error = 0;
739                foundABreak = NO;
740                               
741                for (j = recOffset; j < lineLen; j++) {
742                        ATSLayoutRecord *rec = &records[j];
743                        Fixed recPos = rec->realPos - widthOffset;
744                        UniCharArrayOffset charOffset = rec->originalOffset/2 + lastHardBreak;
745
746                        if (bitfield_test(breakOpportunities, charOffset)) {
747                                if (recPos >= idealLineWidth) {
748                                        error = recPos - idealLineWidth;
749
750                                        if (lastIndex) {
751                                                Fixed lastError = abs((records[lastIndex].realPos - widthOffset) - idealLineWidth);
752                                                if (lastError < error || div->wrapStyle == kSubLineWrapBottomWider) {
753                                                        rec = &records[lastIndex];
754                                                        j = lastIndex;
755                                                        recPos = rec->realPos - widthOffset;
756                                                        charOffset = rec->originalOffset/2 + lastHardBreak;
757                                                }
758                                        }
759                                       
760                                        // try not to leave short trailing lines
761                                        if ((recPos + (originalLineWidth - rec->realPos)) < maximumLineWidth) return 0;
762                                               
763                                        foundABreak = YES;
764                                        lastBreakOffset = charOffset;
765                                        ATSUSetSoftLineBreak(layout, charOffset);
766                                        break;
767                                }
768                               
769                                lastIndex = j;
770                        }
771                }
772               
773                widthOffset = records[j].realPos;
774                recOffset = j;
775                numBreaks--;
776        } while (foundABreak && numBreaks);
777               
778        return (numBreaks == 0) ? 0 : lastBreakOffset;
779}
780
781static void BreakLinesEvenly(ATSUTextLayout layout, SubRenderDiv *div, TextBreakLocatorRef breakLocator, Fixed breakingWidth, unichar *utext, unsigned textLen, ItemCount numHardBreaks)
782{
783        UniCharArrayOffset hardBreaks[numHardBreaks+2];
784        declare_bitfield(breakOpportunities, textLen);
785        float fBreakingWidth = FixedToFloat(breakingWidth);
786        int i;
787       
788        ATSUGetSoftLineBreaks(layout, kATSUFromTextBeginning, kATSUToTextEnd, numHardBreaks, &hardBreaks[1], NULL);     
789        FindAllPossibleLineBreaks(breakLocator, utext, textLen, breakOpportunities);
790               
791        hardBreaks[0] = 0;
792        hardBreaks[numHardBreaks+1] = textLen;
793       
794        for (i = 0; i <= numHardBreaks; i++) {
795                UniCharArrayOffset thisBreak = hardBreaks[i], nextBreak = hardBreaks[i+1];
796                ATSUTextMeasurement leftEdge, rightEdge, ignore;
797               
798                ATSUGetUnjustifiedBounds(layout, thisBreak, nextBreak - thisBreak, &leftEdge, &rightEdge, &ignore, &ignore);
799                Fixed lineWidth = rightEdge - leftEdge;
800                float fLineWidth = FixedToFloat(lineWidth);
801                               
802                if (lineWidth > breakingWidth) {
803                        ATSLayoutRecord *records;
804                        ItemCount numRecords;
805                        unsigned idealSplitLines = ceil(fLineWidth / fBreakingWidth);
806                        Fixed idealBreakWidth = FloatToFixed(fLineWidth / idealSplitLines);
807                       
808                        ATSUDirectGetLayoutDataArrayPtrFromTextLayout(layout, thisBreak, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void*)&records, &numRecords);
809                                               
810                        UniCharArrayOffset res = BreakOneLineSpan(layout, div, breakOpportunities, records, numRecords, idealBreakWidth, lineWidth, breakingWidth, idealSplitLines-1, thisBreak);
811                       
812                        ATSUDirectReleaseLayoutDataArrayPtr(NULL, kATSUDirectDataLayoutRecordATSLayoutRecordCurrent, (void*)&records);
813                       
814                        if (res) ATSUBatchBreakLines(layout, res, nextBreak - res, breakingWidth, NULL);
815                }
816        }
817}
818
819static UniCharArrayOffset *FindLineBreaks(ATSUTextLayout layout, SubRenderDiv *div, TextBreakLocatorRef breakLocator, UniCharArrayOffset *breaks, ItemCount *nbreaks, Fixed breakingWidth, unichar *utext, unsigned textLen)
820{
821        ItemCount breakCount=0;
822       
823        switch (div->wrapStyle) {
824                case kSubLineWrapSimple:
825                        ATSUBatchBreakLines(layout, kATSUFromTextBeginning, kATSUToTextEnd, breakingWidth, &breakCount);
826                        break;
827                case kSubLineWrapTopWider:
828                case kSubLineWrapBottomWider:
829                case kSubLineWrapNone:
830                        SetLayoutPositioning(layout, positiveInfinity, kSubAlignmentLeft);     
831                        ATSUBatchBreakLines(layout, kATSUFromTextBeginning, kATSUToTextEnd, positiveInfinity, &breakCount);
832                        if (div->wrapStyle != kSubLineWrapNone) {
833                                BreakLinesEvenly(layout, div, breakLocator, breakingWidth, utext, textLen, breakCount);
834                                ATSUGetSoftLineBreaks(layout, kATSUFromTextBeginning, kATSUToTextEnd, 0, NULL, &breakCount);
835                        }
836                        SetLayoutPositioning(layout, breakingWidth, div->alignH);       
837                        break;
838        }
839               
840        breaks = realloc(breaks, sizeof(UniCharArrayOffset) * (breakCount+2));
841        ATSUGetSoftLineBreaks(layout, kATSUFromTextBeginning, kATSUToTextEnd, breakCount, &breaks[1], NULL);
842       
843        breaks[0] = 0;
844        breaks[breakCount+1] = textLen;
845       
846        *nbreaks = breakCount;
847        return breaks;
848}
849
850typedef struct {
851        UniCharArrayOffset *breaks;
852        ItemCount breakCount;
853        int lStart, lEnd;
854        SInt8 direction;
855} BreakContext;
856
857enum {kTextLayerShadow, kTextLayerOutline, kTextLayerPrimary};
858
859static BOOL SetupCGForSpan(CGContextRef c, SubATSUISpanEx *spanEx, SubATSUISpanEx *lastSpanEx, SubRenderDiv *div, int textType, BOOL endLayer)
860{       
861#define if_different(x) if (!lastSpanEx || lastSpanEx-> x != spanEx-> x)
862       
863        switch (textType) {
864                case kTextLayerShadow:
865                        if_different(shadowColor) {
866                                if (endLayer) CGContextEndTransparencyLayer(c);
867
868                                SetColor(c, fillc, spanEx->shadowColor);
869                                SetColor(c, strokec, spanEx->shadowColor);
870                                if (CGColorGetAlpha(spanEx->shadowColor) != 1.) {
871                                        endLayer = YES;
872                                        CGContextBeginTransparencyLayer(c, NULL);
873                                } else endLayer = NO;
874                        }
875                        break;
876                       
877                case kTextLayerOutline:
878                        if_different(outlineRadius) CGContextSetLineWidth(c, spanEx->outlineRadius ? (spanEx->outlineRadius*2. + .5) : 0.);
879                        if_different(outlineColor)  SetColor(c, (div->styleLine->borderStyle == kSubBorderStyleNormal) ? strokec : fillc, spanEx->outlineColor);
880                       
881                        if_different(outlineAlpha) {
882                                if (endLayer) CGContextEndTransparencyLayer(c);
883                                CGContextSetAlpha(c, spanEx->outlineAlpha);
884                                if (spanEx->outlineAlpha != 1.) {
885                                        endLayer = YES;
886                                        CGContextBeginTransparencyLayer(c, NULL);
887                                } else endLayer = NO;
888                        }
889                               
890                        break;
891                case kTextLayerPrimary:
892                        if_different(primaryColor) SetColor(c, fillc, spanEx->primaryColor);
893                       
894                        if_different(primaryAlpha) {
895                                if (endLayer) CGContextEndTransparencyLayer(c);
896
897                                CGContextSetAlpha(c, spanEx->primaryAlpha);
898                                if (spanEx->primaryAlpha != 1.) {
899                                        endLayer = YES;
900                                        CGContextBeginTransparencyLayer(c, NULL);
901                                } else endLayer = NO;
902                        }
903                        break;
904        }
905       
906        return endLayer;
907}
908
909static void RenderActualLine(ATSUTextLayout layout, UniCharArrayOffset thisBreak, UniCharArrayOffset lineLen, Fixed penX, Fixed penY, CGContextRef c, SubRenderDiv *div, SubATSUISpanEx *spanEx, int textType)
910{
911        //ATS bug(?) with some fonts:
912        //drawing \n draws some random other character, so skip them
913        //XXX maybe don't store newlines in div->text at all
914        if ([div->text characterAtIndex:thisBreak+lineLen-1] == '\n') {
915                lineLen--;
916                if (!lineLen) return;
917        }
918
919        if (textType == kTextLayerOutline && div->styleLine->borderStyle == kSubBorderStyleBox) {
920                ATSUTextMeasurement lineWidth, lineHeight, lineX, lineY;
921                UniCharArrayOffset breaks[2] = {thisBreak, thisBreak + lineLen};
922                GetTypographicRectangleForLayout(layout, breaks, 0, FloatToFixed(spanEx->outlineRadius), &lineX, &lineY, &lineHeight, &lineWidth);
923               
924                CGRect borderRect = CGRectMake(FixedToFloat(lineX + penX), FixedToFloat(penY - lineY), FixedToFloat(lineWidth), FixedToFloat(lineHeight));
925               
926                ExpandCGRect(&borderRect, spanEx->outlineRadius);
927               
928                borderRect.origin.x = floor(borderRect.origin.x);
929                borderRect.origin.y = floor(borderRect.origin.y);
930                borderRect.size.width = ceil(borderRect.size.width);
931                borderRect.size.height = ceil(borderRect.size.height);
932               
933                CGContextFillRect(c, borderRect);
934        } else ATSUDrawText(layout, thisBreak, lineLen, RoundFixed(penX), RoundFixed(penY));
935}
936
937static Fixed DrawTextLines(CGContextRef c, ATSUTextLayout layout, SubRenderDiv *div, const BreakContext breakc, Fixed penX, Fixed penY, SubATSUISpanEx *firstSpanEx, int textType)
938{
939        int i;
940        BOOL endLayer = NO;
941        SubATSUISpanEx *lastSpanEx = nil;
942        const CGTextDrawingMode textModes[] = {kCGTextFillStroke, kCGTextStroke, kCGTextFill};
943               
944        CGContextSetTextDrawingMode(c, textModes[textType]);
945       
946        if (!(div->render_complexity & renderMultipleParts)) endLayer = SetupCGForSpan(c, firstSpanEx, lastSpanEx, div, textType, endLayer);
947       
948        for (i = breakc.lStart; i != breakc.lEnd; i -= breakc.direction) {
949                UniCharArrayOffset thisBreak = breakc.breaks[i], nextBreak = breakc.breaks[i+1], linelen = nextBreak - thisBreak;
950                float extraHeight = 0;
951               
952                if (!(div->render_complexity & renderMultipleParts)) {
953                        RenderActualLine(layout, thisBreak, linelen, penX, penY, c, div, firstSpanEx, textType);
954                        extraHeight = div->styleLine->outlineRadius;
955                } else {
956                        int j, nspans = [div->spans count];
957                       
958                        //linear search for the next span to draw
959                        //XXX not at all sure how this works or if it's correct
960                        for (j = 0; j < nspans; j++) {
961                                SubRenderSpan *span = [div->spans objectAtIndex:j];
962                                SubATSUISpanEx *spanEx = span->ex;
963                                UniCharArrayOffset spanLen, drawStart, drawLen;
964                               
965                                if (j < nspans-1) {
966                                        SubRenderSpan *nextSpan = [div->spans objectAtIndex:j+1];
967                                        spanLen = nextSpan->offset - span->offset;
968                                } else spanLen = [div->text length] - span->offset;
969                               
970                                if (span->offset < thisBreak) { // text spans a newline
971                                        drawStart = thisBreak;
972                                        drawLen = spanLen - (thisBreak - span->offset);
973                                } else {
974                                        drawStart = span->offset;
975                                        drawLen = MIN(spanLen, nextBreak - span->offset);
976                                }
977                               
978                                if (spanLen == 0 || drawLen == 0) continue;
979                                if ((span->offset + spanLen) < thisBreak) continue; // too early
980                                if (span->offset >= nextBreak) break; // too far ahead
981
982                                endLayer = SetupCGForSpan(c, spanEx, lastSpanEx, div, textType, endLayer);
983                                RenderActualLine(layout, drawStart, drawLen, (textType == kTextLayerShadow) ? (penX + FloatToFixed(spanEx->shadowDist)) : penX,
984                                                                                                                 (textType == kTextLayerShadow) ? (penY - FloatToFixed(spanEx->shadowDist)) : penY, c, div, spanEx, textType);
985                                extraHeight = MAX(extraHeight, spanEx->outlineRadius);
986                                lastSpanEx = spanEx;
987                        }
988               
989                }
990
991                penY += breakc.direction * (GetLineHeight(layout, thisBreak) + FloatToFixed(extraHeight));
992        }
993               
994        if (endLayer) CGContextEndTransparencyLayer(c);
995
996        return penY;
997}
998
999static Fixed DrawOneTextDiv(CGContextRef c, ATSUTextLayout layout, SubRenderDiv *div, const BreakContext breakc, Fixed penX, Fixed penY)
1000{
1001        SubATSUISpanEx *firstSpanEx = ((SubRenderSpan*)[div->spans objectAtIndex:0])->ex;
1002        BOOL endLayer = NO;
1003       
1004        if (div->styleLine->borderStyle == kSubBorderStyleNormal && firstSpanEx->shadowDist) {
1005                if (!(div->render_complexity & renderManualShadows)) {
1006                        endLayer = YES;
1007                        CGContextSetShadowWithColor(c, CGSizeMake(firstSpanEx->shadowDist + .5, -(firstSpanEx->shadowDist + .5)), 0, firstSpanEx->shadowColor);
1008                        CGContextBeginTransparencyLayer(c, NULL);
1009                } else DrawTextLines(c, layout, div, breakc, penX, penY, firstSpanEx, kTextLayerShadow);
1010        }
1011       
1012        DrawTextLines(c, layout, div, breakc, penX, penY, firstSpanEx, kTextLayerOutline);
1013        penY = DrawTextLines(c, layout, div, breakc, penX, penY, firstSpanEx, kTextLayerPrimary);
1014       
1015        if (endLayer) {
1016                CGContextEndTransparencyLayer(c);
1017                CGContextSetShadowWithColor(c, CGSizeMake(0,0), 0, NULL);
1018        }
1019       
1020        return penY;
1021}
1022
1023#pragma mark Main Renderer Function
1024
1025-(void)renderPacket:(NSString *)packet inContext:(CGContextRef)c width:(float)cWidth height:(float)cHeight
1026{
1027        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1028        ubuffer = realloc(ubuffer, sizeof(unichar) * [packet length]);
1029        NSArray *divs = SubParsePacket(packet, context, self, ubuffer);
1030        unsigned div_count = [divs count], lastLayer = 0;
1031        int i;
1032        Fixed bottomPen = 0, topPen = 0, centerPen = 0, *storePen=NULL;
1033       
1034        CGContextSaveGState(c);
1035       
1036        if (cWidth != videoWidth || cHeight != videoHeight) CGContextScaleCTM(c, cWidth / videoWidth, cHeight / videoHeight);
1037        SetATSULayoutOther(layout, kATSUCGContextTag, sizeof(CGContextRef), &c);
1038       
1039        CGContextSetLineCap(c, kCGLineCapRound);
1040        CGContextSetLineJoin(c, kCGLineJoinRound);
1041        CGContextSetInterpolationQuality(c, kCGInterpolationHigh);
1042        CGContextSetShouldSmoothFonts(c, NO); //disables subpixel AA for some reason
1043       
1044        for (i = 0; i < div_count; i++) {
1045                SubRenderDiv *div = [divs objectAtIndex:i];
1046                unsigned textLen = [div->text length];
1047                BOOL resetPens = NO;
1048                if (!textLen || ![div->spans count]) continue;
1049                                               
1050                if (div->layer != lastLayer) {
1051                        resetPens = YES;
1052                        lastLayer = div->layer;
1053                }
1054                               
1055                NSRect marginRect = NSMakeRect(div->marginL, div->marginV, context->resX - div->marginL - div->marginR, context->resY - div->marginV - div->marginV);
1056               
1057                marginRect.origin.x *= screenScaleX;
1058                marginRect.origin.y *= screenScaleY;
1059                marginRect.size.width *= screenScaleX;
1060                marginRect.size.height *= screenScaleY;
1061
1062                Fixed penY, penX, breakingWidth = FloatToFixed(marginRect.size.width);
1063                BreakContext breakc = {0}; ItemCount breakCount;
1064
1065                [div->text getCharacters:ubuffer];
1066               
1067                ATSUSetTextPointerLocation(layout, ubuffer, kATSUFromTextBeginning, kATSUToTextEnd, textLen);           
1068                ATSUSetTransientFontMatching(layout,TRUE);
1069               
1070                SetLayoutPositioning(layout, breakingWidth, div->alignH);
1071                SetStyleSpanRuns(layout, div);
1072               
1073                breakbuffer = FindLineBreaks(layout, div, breakLocator, breakbuffer, &breakCount, breakingWidth, ubuffer, textLen);
1074
1075                ATSUTextMeasurement imageWidth, imageHeight;
1076                UniCharArrayOffset *breaks = breakbuffer;
1077
1078                if (div->positioned || div->alignV == kSubAlignmentMiddle)
1079                        GetTypographicRectangleForLayout(layout, breaks, breakCount, FloatToFixed(div->styleLine->outlineRadius), NULL, NULL, &imageHeight, &imageWidth);
1080
1081                if (!div->positioned) {
1082                        penX = FloatToFixed(NSMinX(marginRect));
1083
1084                        switch(div->alignV) {
1085                                case kSubAlignmentBottom: default:
1086                                        if (!bottomPen || resetPens) {
1087                                                ATSUTextMeasurement bottomLineDescent;
1088                                                ATSUGetLineControl(layout, kATSUFromTextBeginning, kATSULineDescentTag, sizeof(ATSUTextMeasurement), &bottomLineDescent, NULL);
1089                                                penY = FloatToFixed(NSMinY(marginRect)) + bottomLineDescent;
1090                                        } else penY = bottomPen;
1091                                       
1092                                        storePen = &bottomPen; breakc.lStart = breakCount; breakc.lEnd = -1; breakc.direction = 1;
1093                                        break;
1094                                case kSubAlignmentMiddle:
1095                                        if (!centerPen || resetPens) {
1096                                                penY = (FloatToFixed(NSMidY(marginRect)) / 2) + (imageHeight / 2);
1097                                        } else penY = centerPen;
1098                                       
1099                                        storePen = &centerPen; breakc.lStart = breakCount; breakc.lEnd = -1; breakc.direction = 1;
1100                                        break;
1101                                case kSubAlignmentTop:
1102                                        if (!topPen || resetPens) {
1103                                                penY = FloatToFixed(NSMaxY(marginRect)) - GetLineHeight(layout, kATSUFromTextBeginning);
1104                                        } else penY = topPen;
1105                                       
1106                                        storePen = &topPen; breakc.lStart = 0; breakc.lEnd = breakCount+1; breakc.direction = -1;
1107                                        break;
1108                        }
1109                } else {
1110                        ATSUTextMeasurement descent;
1111                        penX = FloatToFixed(div->posX * screenScaleX);
1112                        penY = FloatToFixed((context->resY - div->posY) * screenScaleY);
1113                       
1114                        switch (div->alignH) {
1115                                case kSubAlignmentCenter: penX -= imageWidth / 2; break;
1116                                case kSubAlignmentRight: penX -= imageWidth;
1117                        }
1118                       
1119                        ATSUGetLineControl(layout, kATSUFromTextBeginning, kATSULineDescentTag, sizeof(ATSUTextMeasurement), &descent, NULL);
1120
1121                        switch (div->alignV) {
1122                                case kSubAlignmentMiddle: penY -= imageHeight / 2; break;
1123                                case kSubAlignmentTop: penY -= imageHeight; break;
1124                        }
1125                       
1126                        penY += descent;
1127
1128                        SetLayoutPositioning(layout, imageWidth, div->alignH);
1129                        storePen = NULL; breakc.lStart = breakCount; breakc.lEnd = -1; breakc.direction = 1;
1130                }
1131               
1132                {
1133                        SubATSUISpanEx *firstspan = ((SubRenderSpan*)[div->spans objectAtIndex:0])->ex;
1134                        Fixed fangle = FloatToFixed(firstspan->angle);
1135                       
1136                        SetATSULayoutOther(layout, kATSULineRotationTag, sizeof(Fixed), &fangle);
1137                }
1138               
1139                breakc.breakCount = breakCount;
1140                breakc.breaks = breaks;
1141               
1142                penY = DrawOneTextDiv(c, layout, div, breakc, penX, penY);
1143               
1144                if (storePen) *storePen = penY;
1145        }
1146       
1147        CGContextRestoreGState(c);
1148        [pool release];
1149}
1150
1151SubtitleRendererPtr SubInitForSSA(char *header, size_t headerLen, int width, int height)
1152{
1153        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1154        NSString *hdr = [[NSString alloc] initWithBytesNoCopy:(void*)header length:headerLen encoding:NSUTF8StringEncoding freeWhenDone:NO];
1155
1156        SubtitleRendererPtr s = [[SubATSUIRenderer alloc] initWithSSAHeader:hdr videoWidth:width videoHeight:height];
1157        [hdr release];
1158        CFRetain(s);
1159        [pool release];
1160        return s;
1161}
1162
1163SubtitleRendererPtr SubInitNonSSA(int width, int height)
1164{
1165        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1166        SubtitleRendererPtr s = [[SubATSUIRenderer alloc] initWithVideoWidth:width videoHeight:height];
1167        CFRetain(s);
1168        [pool release];
1169        return s;
1170}
1171
1172CGColorSpaceRef SubGetColorSpace(SubtitleRendererPtr s)
1173{
1174        return s->srgbCSpace;
1175}
1176
1177void SubRenderPacket(SubtitleRendererPtr s, CGContextRef c, CFStringRef str, int cWidth, int cHeight)
1178{
1179        [s renderPacket:(NSString*)str inContext:c width:cWidth height:cHeight];
1180}
1181
1182void SubPrerollFromHeader(char *header, int headerLen)
1183{
1184        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1185        SubtitleRendererPtr s = headerLen ? SubInitForSSA(header, headerLen, 640, 480)
1186                                                                      : SubInitNonSSA(640, 480);
1187        /*
1188        CGColorSpaceRef csp = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
1189        void *buf = malloc(640 * 480 * 4);
1190        CGContextRef c = CGBitmapContextCreate(buf,640,480,8,640 * 4,csp,kCGImageAlphaPremultipliedFirst);
1191       
1192        if (!headerLen) {
1193                SubRenderPacket(s, c, (CFStringRef)@"Abcde .", 640, 480);
1194        } else {
1195                NSArray *styles = [s->context->styles allValues];
1196                int i, nstyles = [styles count];
1197               
1198                for (i = 0; i < nstyles; i++) {
1199                        SubStyle *sty = [styles objectAtIndex:i];
1200                        NSString *line = [NSString stringWithFormat:@"0,0,%@,,0,0,0,,Abcde .", sty->name];
1201                        SubRenderPacket(s, c, (CFStringRef)line, 640, 480);
1202                }
1203        }
1204       
1205        CGContextRelease(c);
1206        free(buf);
1207        CGColorSpaceRelease(csp);
1208        */
1209       
1210        SubDisposeRenderer(s);
1211        [pool release];
1212}
1213
1214void SubDisposeRenderer(SubtitleRendererPtr s)
1215{
1216        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1217        CFRelease(s);
1218        [s release];
1219        [pool release];
1220}
1221@end
1222
1223#pragma options align=mac68k
1224typedef struct TT_Header
1225{
1226    Fixed   Table_Version;
1227    Fixed   Font_Revision;
1228       
1229    SInt32  CheckSum_Adjust;
1230    SInt32  Magic_Number;
1231       
1232    UInt16  Flags;
1233    UInt16  Units_Per_EM;
1234       
1235    SInt32  Created [2];
1236    SInt32  Modified[2];
1237       
1238    SInt16  xMin;
1239    SInt16  yMin;
1240    SInt16  xMax;
1241    SInt16  yMax;
1242       
1243    UInt16  Mac_Style;
1244    UInt16  Lowest_Rec_PPEM;
1245       
1246    SInt16  Font_Direction;
1247    SInt16  Index_To_Loc_Format;
1248    SInt16  Glyph_Data_Format;
1249       
1250} TT_Header;
1251
1252//Windows/OS/2 TrueType metrics table
1253typedef struct TT_OS2
1254{
1255    UInt16   version;                /* 0x0001 - more or 0xFFFF */
1256    SInt16   xAvgCharWidth;
1257    UInt16   usWeightClass;
1258    UInt16   usWidthClass;
1259    SInt16   fsType;
1260    SInt16   ySubscriptXSize;
1261    SInt16   ySubscriptYSize;
1262    SInt16   ySubscriptXOffset;
1263    SInt16   ySubscriptYOffset;
1264    SInt16   ySuperscriptXSize;
1265    SInt16   ySuperscriptYSize;
1266    SInt16   ySuperscriptXOffset;
1267    SInt16   ySuperscriptYOffset;
1268    SInt16   yStrikeoutSize;
1269    SInt16   yStrikeoutPosition;
1270    SInt16   sFamilyClass;
1271       
1272    UInt8    panose[10];
1273       
1274    UInt32   ulUnicodeRange1;        /* Bits 0-31   */
1275    UInt32   ulUnicodeRange2;        /* Bits 32-63  */
1276    UInt32   ulUnicodeRange3;        /* Bits 64-95  */
1277    UInt32   ulUnicodeRange4;        /* Bits 96-127 */
1278       
1279    SInt8    achVendID[4];
1280       
1281    UInt16   fsSelection;
1282    UInt16   usFirstCharIndex;
1283    UInt16   usLastCharIndex;
1284    SInt16   sTypoAscender;
1285    SInt16   sTypoDescender;
1286    SInt16   sTypoLineGap;
1287    UInt16   usWinAscent;
1288    UInt16   usWinDescent;
1289       
1290    /* only version 1 tables: */
1291       
1292    UInt32   ulCodePageRange1;       /* Bits 0-31   */
1293    UInt32   ulCodePageRange2;       /* Bits 32-63  */
1294       
1295    /* only version 2 tables: */
1296       
1297    SInt16   sxHeight;
1298    SInt16   sCapHeight;
1299    UInt16   usDefaultChar;
1300    UInt16   usBreakChar;
1301    UInt16   usMaxContext;
1302       
1303} TT_OS2;
1304#pragma options align=reset
1305
1306// Windows and OS X use different TrueType fields to measure text.
1307// Some Windows fonts have one field set incorrectly, so we have to compensate.
1308// XXX This function doesn't read from the right fonts; if we're using italic variant, it should get the ATSFontRef for that
1309static float GetWinFontSizeScale(ATSFontRef font)
1310{
1311        TT_Header headTable = {0};
1312        TT_OS2 os2Table = {0};
1313        ByteCount os2Size = 0, headSize = 0;
1314       
1315        OSErr err = ATSFontGetTable(font, 'OS/2', 0, 0, NULL, &os2Size);
1316        if (!os2Size || err) return 1;
1317       
1318        err = ATSFontGetTable(font, 'head', 0, 0, NULL, &headSize);
1319        if (!headSize || err) return 1;
1320
1321        ATSFontGetTable(font, 'head', 0, headSize, &headTable, &headSize);
1322        ATSFontGetTable(font, 'OS/2', 0, os2Size, &os2Table, &os2Size);
1323               
1324        // ppem = units_per_em * lfheight / (winAscent + winDescent) c.f. WINE
1325        // lfheight being SSA font size
1326        unsigned oA = EndianU16_BtoN(os2Table.usWinAscent), oD = EndianU16_BtoN(os2Table.usWinDescent);
1327        unsigned winSize = oA + oD;
1328               
1329        unsigned unitsPerEM = EndianU16_BtoN(headTable.Units_Per_EM);
1330       
1331        return (winSize && unitsPerEM) ? ((float)unitsPerEM / (float)winSize) : 1;
1332}
1333
1334static void FindAllPossibleLineBreaks(TextBreakLocatorRef breakLocator, unichar *uline, UniCharArrayOffset lineLen, unsigned char *breakOpportunities)
1335{
1336        UniCharArrayOffset lastBreak = 0;
1337       
1338        while (1) {
1339                UniCharArrayOffset breakOffset = 0;
1340                OSStatus status;
1341               
1342                status = UCFindTextBreak(breakLocator, kUCTextBreakLineMask, kUCTextBreakLeadingEdgeMask | (lastBreak ? kUCTextBreakIterateMask : 0), uline, lineLen, lastBreak, &breakOffset);
1343               
1344                if (status != noErr || breakOffset >= lineLen) break;
1345               
1346                bitfield_set(breakOpportunities, breakOffset-1);
1347                lastBreak = breakOffset;
1348        }
1349}
Note: See TracBrowser for help on using the repository browser.