source: trunk/Subtitles/SubATSUIRenderer.m @ 1044

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