source: trunk/Subtitles/SubATSUIRenderer.m @ 1393

Revision 1393, 47.6 KB checked in by astrange, 3 years ago (diff)

Revert r1324 to fix 10.4/AppleTV.

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