source: trunk/Subtitles/SubATSUIRenderer.m @ 1323

Revision 1323, 47.4 KB checked in by astrange, 4 years ago (diff)

SSA: Eliminate a silly lock/unlock pattern in favor of a slightly less silly pattern

Too bad there's no PTHREAD_RECURSIVE_MUTEX_INITIALIZER.
I don't really want to use constructor attributes but they might be simplest.

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