source: trunk/Subtitles/SubATSUIRenderer.m @ 1103

Revision 1103, 46.8 KB checked in by astrange, 5 years ago (diff)

SSA: Reset the current pen for a line with \org.

Note that I still think rendering of the next line will be wrong.
And the sample file is still broken differently (like text is being drawn in the wrong order).
refs #427

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