source: trunk/Subtitles/SubATSUIRenderer.m @ 1163

Revision 1163, 47.5 KB checked in by astrange, 5 years ago (diff)

SSA: Fix toptitles being too low.

Ignoring the descent brings us closer to the
accurate positioning for them.

Especially improves [Mazui]_To_Aru_Kagaku_no_Railgun_-_02v2_[C1073458].mkv.

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