//
//  ECLayer.m
//  SceneRenderProto
//
//  Created by 二鏡 on 11/11/13.
//  Copyright 2011年 二鏡庵. All rights reserved.
//

#import "ECRegularLayer.h"
#import "ServiceFunctions.h"
#import "ECScene.h"

NSString *ECPasteboardTypeRegularLayer = @"com.mac.nikyo-an.pasteboard.regularLayer";

static NSString *plistImageKey = @"image"; // coding only

static NSString *plistStringKey = @"string";
static NSString *plistFontKey = @"font";
static NSString *plistFontColorKey = @"fontColor";
static NSString *plistLineSpacingKey = @"lineSpacing";
static NSString *plistAlignmentKey = @"alignment";
static NSString *plistVerticalKey = @"vertical";
static NSString *plistCountTextWait = @"countTextWait";
static NSString *plistTextWaitKey = @"textWait";
static NSString *plistUseTextWaitKey = @"useTextWait";
static NSString *plistTextFrameKey = @"textFrame";
static NSString *plistPostEffectKey = @"postEffect";

// version 1.1
static NSString *plistUseTextStorkeKey = @"useTextStroke";
static NSString *plistUseTextShadowKey = @"useTextShadow";
static NSString *plistTextStrokeColorKey = @"textStrokeColor";

// draw a run. compatible both horizontal/vertical.
static inline void
_drawRun(CTRunRef run, CGContextRef context)
{
    // Core Textの描画メソッドはcontextがメインスレッド上で動作すると仮定しており、
    // バックグラウンドのBitmapContextに描画を掛けるとエラーを吐きまくる。FUCK!
    // よって自力でQuartzを叩かねばならない！

    CFIndex count = CTRunGetGlyphCount(run);
    CGGlyph *buf = alloca(sizeof(CGGlyph)*count);
    CGPoint *pos = alloca(sizeof(CGPoint)*count);
    CTRunGetGlyphs(run, CFRangeMake(0,0), buf);
    CTRunGetPositions(run, CFRangeMake(0,0), pos);
    CGContextShowGlyphsAtPositions(context, buf, pos, count);
}

static inline CGContextRef
_CGContextCreateForComposition(CGSize size)
{
    size_t w = floor(size.width);
    size_t h = floor(size.height);
    size_t bitsPerComponent = 8; // 32bits color
    size_t bytesPerRow = w*4; // 4bytes per pixel
    
    if(w <= gMaxImageSize && h <= gMaxImageSize)
    {
        CGColorSpaceRef cs = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
        CGBitmapInfo info = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrderDefault;
        CGContextRef ret = CGBitmapContextCreate(NULL, w, h, bitsPerComponent, bytesPerRow, cs, info);
        CGColorSpaceRelease(cs);
        return ret;
    }
    else
    {
        return nil;
    }
}

static inline CGLayerRef
_CGLayerCreateForCache(CGContextRef aContext, CGSize size)
{
    CGRect layerRect;
    layerRect.size = size;
    layerRect.origin = NSZeroPoint;
    return CGLayerCreateWithContext(aContext, size, nil);
}

static inline QCRenderer *
_QCRendererCreate(QCComposition *composition, id params, CGSize size)
{
    id ret = [[QCRenderer alloc] initOffScreenWithSize: size
                                            colorSpace: nil
                                           composition: composition];
    [ret setInputValuesWithPropertyList: params];
    return ret;
}

@interface ECRegularLayer (Internal)
// 現在のテキストを計算。タイプセッターの生成に使う
- (NSAttributedString*)_attributedString;

- (CGImageRef)allocSnapshotAtTime_:(CGFloat)duration
                          textLimit:(CFIndex)limit;
- (void)_compositeText:(CGContextRef)aContext
                 limit:(CFIndex)limit;
// テキストを合成。タイプセッターが用意されていること
- (void)_compositeVerticalText:(CGContextRef)aContext
                         limit:(CFIndex)limit;
- (void)_compositeHorizontalText:(CGContextRef)aContext
                           limit:(CFIndex)limit;
- (void)_compositeImage:(CGContextRef)aContext;
@end

@implementation ECRegularLayer
@synthesize countTextWait, useTextWait = useTextWait, textWait = textWait;

static void inline
_clear_cache(ECRegularLayer *layer)
{
    if(layer->cache)
    {
        CFRelease(layer->cache);
        layer->cache = nil;
    }
}

static void inline
_clear_setter(ECRegularLayer *layer)
{
    if(layer->typesetter)
    {
        CFRelease(layer->typesetter);
        layer->typesetter = nil;
    }
}

+ (NSSet*)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
    if([key isEqualToString: @"textX"])
        return [NSSet setWithObject: @"textFrame"];
    if([key isEqualToString: @"textY"])
        return [NSSet setWithObject: @"textFrame"];
    if([key isEqualToString: @"textWidth"])
        return [NSSet setWithObject: @"textFrame"];
    if([key isEqualToString: @"textHeight"])
        return [NSSet setWithObject: @"textFrame"];
    return [super keyPathsForValuesAffectingValueForKey: key];
}

- (id)initWithImage:(ECImageRef*)aImage
               size:(CGSize)aSize
{
    self = [super init];
    if(self)
    {
        alpha = 1.0;
        imageRef = [aImage retain];
        size = aSize;
        textFrame.size = aSize;
        font = [[NSFont systemFontOfSize: 12.0] retain]; // dummy font
        string = @"";
        textWait = 50;
        [self setFontColor: [NSColor whiteColor]];
        [self setTextStrokeColor: [NSColor blackColor]];
    }
    return self;
}

- (void)dealloc
{
    _clear_cache(self);
    _clear_setter(self);
    [effector release];
    [postEffect release];
    [imageRef release];
    [string release];
    [font release];
    CGColorRelease(fontColor);
    CGColorRelease(textStrokeColor);
    
    [super dealloc];
}

#pragma mark NSCoding
- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder: aDecoder];
    if(self)
    {
        imageRef = [[aDecoder decodeObjectForKey: plistImageKey] retain];
        string = [[aDecoder decodeObjectForKey: plistStringKey] retain];
        font = [[aDecoder decodeObjectForKey: plistFontKey] retain];
        self.fontColor = [aDecoder decodeObjectForKey: plistFontColorKey];
        lineSpacing = [aDecoder decodeFloatForKey: plistLineSpacingKey];
        alignment = [aDecoder decodeIntegerForKey: plistAlignmentKey];
        countTextWait = [aDecoder decodeBoolForKey: plistCountTextWait];
        isVertical = [aDecoder decodeBoolForKey: plistVerticalKey];
        useTextWait = [aDecoder decodeBoolForKey: plistUseTextWaitKey];
        textWait = [aDecoder decodeIntegerForKey: plistTextWaitKey];
        textFrame = [aDecoder decodeRectForKey: plistTextFrameKey];
        postEffect = [aDecoder decodeObjectForKey: plistPostEffectKey];
        self.textStrokeColor = [aDecoder decodeObjectForKey: plistTextStrokeColorKey];
        useTextStroke = [aDecoder decodeBoolForKey: plistUseTextStorkeKey];
        useTextShadow = [aDecoder decodeBoolForKey: plistUseTextShadowKey];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [super encodeWithCoder: aCoder];
    [aCoder encodeObject: imageRef forKey: plistImageKey];
    [aCoder encodeObject: string forKey: plistStringKey];
    [aCoder encodeObject: font forKey: plistFontKey];
    [aCoder encodeObject: self.fontColor forKey: plistFontColorKey];
    [aCoder encodeFloat: lineSpacing forKey: plistLineSpacingKey];
    [aCoder encodeInteger: alignment forKey: plistAlignmentKey];
    [aCoder encodeBool: countTextWait forKey: plistCountTextWait];
    [aCoder encodeBool: isVertical forKey: plistVerticalKey];
    [aCoder encodeBool: useTextWait forKey: plistUseTextWaitKey];
    [aCoder encodeInteger: textWait forKey: plistTextWaitKey];
    [aCoder encodeRect: textFrame forKey: plistTextFrameKey];
    [aCoder encodeObject: postEffect forKey: plistPostEffectKey];
    [aCoder encodeObject: self.textStrokeColor forKey: plistTextStrokeColorKey];
    [aCoder encodeBool: useTextStroke forKey: plistUseTextStorkeKey];
    [aCoder encodeBool: useTextShadow forKey: plistUseTextShadowKey];
}

#pragma mark NSCopying
- (id)copyWithZone:(NSZone *)zone
{
    ECRegularLayer *ret = [super copyWithZone: zone];
    ret->imageRef = [imageRef retain];
    ret->string = [string copy];
    ret->font = [font retain];
    ret->fontColor = CGColorRetain(fontColor);
    ret->lineSpacing = lineSpacing;
    ret->alignment = alignment;
    ret->countTextWait = countTextWait;
    ret->isVertical = isVertical;
    ret->useTextWait = useTextWait;
    ret->textWait = textWait;
    ret->textFrame = textFrame;
    ret->postEffect = [postEffect copy];
    
    // verson 1.1
    ret->textStrokeColor = CGColorRetain(textStrokeColor);
    ret->useTextStroke = useTextStroke;
    ret->useTextShadow = useTextShadow;
    
    return ret;
}

#pragma mark NSPasteboardItem
-(NSArray*)writableTypesForPasteboard:(NSPasteboard*)pboard
{
    return [NSArray arrayWithObject: ECPasteboardTypeRegularLayer];
}

-(id)pasteboardPropertyListForType:(NSString*)type
{
    if([type isEqualToString: ECPasteboardTypeRegularLayer])
        return [NSKeyedArchiver archivedDataWithRootObject: self];
    return nil;
}

+ (NSArray *)readableTypesForPasteboard:(NSPasteboard *)pasteboard
{
    static NSArray *types = nil;
    if(!types)
        types = [[NSArray alloc] initWithObjects: ECPasteboardTypeRegularLayer,nil];
    return types;
}

+ (NSPasteboardReadingOptions)readingOptionsForType:(NSString *)type 
                                         pasteboard:(NSPasteboard *)pboard
{
    if([type isEqualToString: ECPasteboardTypeRegularLayer])
        return NSPasteboardReadingAsKeyedArchive;
    return 0;
}
#pragma mark Layer Basic
+ (NSString*)type
{
    return @"Regular";
}

- (id)initWithPropertyList:(NSDictionary*)plist
                packageURL:(NSURL*)packageURL;
{
    self = [super initWithPropertyList: plist 
                            packageURL: packageURL];
    id imgName = [plist objectForKey: plistLayerAuxResourceNameKey];
    if(imgName != nil)
    {
        id imgURL = _layerResourceURLForName(packageURL, imgName);
        imageRef = [[ECImageRef alloc] initWithURL: imgURL];
    }

    string = [[plist objectForKey: plistStringKey] copy];

    id font_ = [plist objectForKey: plistFontKey];
    id fontColor_ = [plist objectForKey: plistFontColorKey];
    font = [[NSUnarchiver unarchiveObjectWithData: font_] copy];
    self.fontColor = [NSUnarchiver unarchiveObjectWithData: fontColor_];

    id postEffect_ = [plist objectForKey: plistPostEffectKey];
    if(postEffect_)
        postEffect = [[NSUnarchiver unarchiveObjectWithData: postEffect_] copy];

    id lineSpacing_ = [plist objectForKey: plistLineSpacingKey];
    id alignment_ = [plist objectForKey: plistAlignmentKey];
    id isVertical_ = [plist objectForKey: plistVerticalKey];
    id countTextWait_ = [plist objectForKey: plistCountTextWait];
    id useTextWait_ = [plist objectForKey: plistUseTextWaitKey];
    id textWait_ = [plist objectForKey: plistTextWaitKey];
    id textFrame_ = [plist objectForKey: plistTextFrameKey];
    
    lineSpacing = [lineSpacing_ floatValue];
    alignment = [alignment_ unsignedIntegerValue];
    isVertical = [isVertical_ boolValue];
    countTextWait = [countTextWait_ boolValue];
    useTextWait = [useTextWait_ boolValue];
    textWait = [textWait_ unsignedIntegerValue];
    textFrame = _rectFromData(textFrame_);
    
    // version 1.1
    id textStrokeColor_ = [plist objectForKey: plistTextStrokeColorKey];
    id useTextStroke_ = [plist objectForKey: plistUseTextStorkeKey];
    id useTextShadow_ = [plist objectForKey: plistUseTextShadowKey];
    if(textStrokeColor_)
        self.textStrokeColor = [NSUnarchiver unarchiveObjectWithData: textStrokeColor_];
    else
        self.textStrokeColor = [NSColor blackColor];
    useTextStroke = [useTextStroke_ boolValue]; // nil -> NO 互換
    useTextShadow = [useTextShadow_ boolValue]; // nil -> NO 互換
    
    return self;
}

- (NSURL*)auxResourceURL
{
    return [imageRef URL]; // includes nil
}

- (id)propertyListWithAuxResourceName:(NSString*)filename
{
    id plist = [super propertyListWithAuxResourceName: filename];
    if(string)
        [plist setObject: string forKey: plistStringKey];
    
    id font_ = [NSArchiver archivedDataWithRootObject: font];
    id fontColor_ = [NSArchiver archivedDataWithRootObject: self.fontColor];
    
    id lineSpacing_ = [NSNumber numberWithFloat: lineSpacing];
    id alignment_ = [NSNumber numberWithUnsignedInteger: alignment];
    id isVertical_ = [NSNumber numberWithBool: isVertical];
    id countTextWait_ = [NSNumber numberWithBool: countTextWait];
    id useTextWait_ = [NSNumber numberWithBool: useTextWait];
    id textWait_ = [NSNumber numberWithUnsignedInteger: textWait];
    id textFrame_ = _rectToData(textFrame);
    
    // version 1.1
    id textStrokeColor_ = [NSArchiver archivedDataWithRootObject: self.textStrokeColor];
    id useTextStroke_ = [NSNumber numberWithBool: useTextStroke];
    id useTextShadow_ = [NSNumber numberWithBool: useTextShadow];
    
    
    [plist setObject: font_ forKey: plistFontKey];
    [plist setObject: fontColor_ forKey: plistFontColorKey];
    [plist setObject: lineSpacing_ forKey: plistLineSpacingKey];
    [plist setObject: alignment_ forKey: plistAlignmentKey];
    [plist setObject: isVertical_ forKey: plistVerticalKey];
    [plist setObject: countTextWait_ forKey: plistCountTextWait];
    [plist setObject: useTextWait_ forKey: plistUseTextWaitKey];
    [plist setObject: textWait_ forKey: plistTextWaitKey];
    [plist setObject: textFrame_ forKey: plistTextFrameKey];
    
    // version 1.1
    [plist setObject: textStrokeColor_ forKey: plistTextStrokeColorKey];
    [plist setObject: useTextStroke_ forKey: plistUseTextStorkeKey];
    [plist setObject: useTextShadow_ forKey: plistUseTextShadowKey];
    

    if(postEffect)
    {
        id postEffect_ = [NSArchiver archivedDataWithRootObject: postEffect];
        [plist setObject: postEffect_ forKey: plistPostEffectKey];
    }
    
    return [[plist copy] autorelease];
}

- (BOOL)isStatic
{
    if(postEffect||effector)
        return NO;
    if(useTextWait)
    {
        if(textWait == 0)
            return YES;
        else
            return NO;
    }
    return YES;
}

+ (id)undoObservationKeys
{
    id ret = [super undoObservationKeys];
    id keys[] = {@"image", @"string",@"font",@"fontColor", @"verticalText", @"lineSpacing", @"textFrame",@"countTextWait", @"useTextWait", @"textWait", @"alignment", @"compositionItem",
        // version 1.1
        @"textStrokeColor", @"useTextStroke", @"useTextShadow",
    };
    [ret addObjects:keys count: sizeof(keys)/sizeof(id)];
    return [[ret copy] autorelease];
}

#pragma mark -

#pragma mark Specialized Property
- (ECImageRef*)image
{
    return imageRef;
}

- (void)setImage:(ECImageRef*)aImage
{
    if(imageRef == aImage)
        return;
    [imageRef release];
    imageRef = [aImage retain];
    _clear_cache(self);
}

- (CGPoint)origin
{
    return origin;
}

- (void)setOrigin:(CGPoint)aPoint
{
    origin = aPoint;
    _clear_cache(self);
}

- (CGSize)size
{
    return size;
}

- (void)setSize:(CGSize)aSize
{
    CGFloat dx = aSize.width - size.width;
    CGFloat dy = aSize.height - size.height;
    size = aSize;

    _clear_cache(self);

    // text frameも追尾変形
    CGFloat x = CGRectGetMinX(textFrame);
    CGFloat y = CGRectGetMinY(textFrame);
    CGFloat w = CGRectGetWidth(textFrame) + dx;
    CGFloat h = CGRectGetHeight(textFrame) + dy;
    
    CGRect rect;
    if(w < 0 || h < 0)
        rect = CGRectMake(0,0,aSize.width,aSize.height);
    else
        rect = CGRectMake(x,y,w,h);
    
    [self setTextFrame: rect];
}

- (NSString*)string
{
    return string;
}

- (void)setString:(NSString*)aString
{
    if(aString == string)
        return;
    [string release];
    string = [aString copy];
    _clear_cache(self);
    _clear_setter(self);
}

- (NSFont*)font
{
    return font;
}

- (void)setFont:(NSFont*)aFont
{
    if(font == aFont)
        return;
    [font release];
    font = [aFont retain];
    _clear_cache(self);
    _clear_setter(self);
}

- (NSString*)fontName
{
    id fontName = [font displayName];
    return [NSString stringWithFormat: @"%@ %.1fpt.",fontName, [font pointSize]];
}

- (NSColor*)fontColor
{
    const CGFloat *comps = CGColorGetComponents(fontColor);
    return [NSColor colorWithCalibratedRed: comps[0] green: comps[1] blue: comps[2] alpha: 1.0];
}

- (void)setFontColor:(NSColor*)aColor
{
    NSAssert(aColor != nil, @"Layer dose not accept nil color.");

    CGColorRelease(fontColor);
    
    id rgbColor = [aColor colorUsingColorSpace: [NSColorSpace genericRGBColorSpace]];
    CGFloat red,green,blue;
    [rgbColor getRed: &red green: &green blue: &blue alpha: NULL];
    fontColor = CGColorCreateGenericRGB(red, green, blue, 1.0);

    _clear_cache(self);
    _clear_setter(self);
}

- (NSTextAlignment)alignment
{
    return alignment;
}

- (void)setAlignment:(NSTextAlignment)val
{
    alignment = val;
    _clear_cache(self);
    _clear_setter(self);
}
   
- (CGFloat)lineSpacing
{
    return lineSpacing;
}

- (void)setLineSpacing:(CGFloat)val
{
    lineSpacing = val;
    _clear_cache(self);
    _clear_setter(self);
}

- (CGRect)textFrame
{
    return textFrame;
}

- (void)setTextFrame:(CGRect)aRect
{
    textFrame = aRect;
    _clear_cache(self);
}

- (BOOL)verticalText
{
    return isVertical;
}

- (void)setVerticalText:(BOOL)flag
{
    isVertical = flag;
    _clear_cache(self);
    _clear_setter(self);
}

- (ECQCCompositionItem*)compositionItem
{
    return [[postEffect copy] autorelease];
}

- (void)setCompositionItem:(ECQCCompositionItem*)item
{
    if(postEffect == item)
        return ;
    [postEffect release];
    postEffect = [item copy];
    _clear_cache(self);
    [effector release]; effector = nil;
}

#pragma mark Version 1.1
- (NSColor*)textStrokeColor
{
    const CGFloat *comps = CGColorGetComponents(textStrokeColor);
    return [NSColor colorWithCalibratedRed: comps[0] green: comps[1] blue: comps[2] alpha: 1.0];
}

- (void)setTextStrokeColor:(NSColor*)aColor
{
    NSAssert(aColor != nil, @"Layer dose not accept nil color.");
    
    CGColorRelease(textStrokeColor);
    
    id rgbColor = [aColor colorUsingColorSpace: [NSColorSpace genericRGBColorSpace]];
    CGFloat red,green,blue;
    [rgbColor getRed: &red green: &green blue: &blue alpha: NULL];
    textStrokeColor = CGColorCreateGenericRGB(red, green, blue, 1.0);
    
    _clear_cache(self);
    _clear_setter(self);
}

- (BOOL)useTextStroke
{
    return useTextStroke;
}

- (void)setUseTextStroke:(BOOL)val
{
    useTextStroke = val;
    _clear_cache(self);
    _clear_setter(self);
}

- (BOOL)useTextShadow
{
    return useTextShadow;
}

- (void)setUseTextShadow:(BOOL)val
{
    useTextShadow = val;
    _clear_cache(self);
    _clear_setter(self);
}

#pragma mark text sub property
- (CGFloat)textX
{
    return CGRectGetMinX(textFrame);
}

- (CGFloat)textY
{
    return CGRectGetMinY(textFrame);
}

- (CGFloat)textWidth
{
    return CGRectGetWidth(textFrame);
}

- (CGFloat)textHeight
{
    return CGRectGetHeight(textFrame);
}

- (void)setTextX:(CGFloat)x
{
    CGRect rect = textFrame;
    rect.origin.x = x;
    self.textFrame = rect;
}

- (void)setTextY:(CGFloat)y
{
    CGRect rect = textFrame;
    rect.origin.y = y;
    self.textFrame = rect;
}

- (void)setTextWidth:(CGFloat)width
{
    CGRect rect = textFrame;
    rect.size.width = width;
    self.textFrame = rect;
}

- (void)setTextHeight:(CGFloat)height
{
    CGRect rect = textFrame;
    rect.size.height = height;
    self.textFrame = rect;
}


#pragma mark Render
- (void)renderAtTime:(NSUInteger)msec
           inContext:(CGContextRef)context
{
    NSAssert(scene != nil, @"Layer's scene property cannot be nil.");
    
    CFIndex limit = LONG_MAX;
    if(self.useTextWait)
    {
        if(self.textWait != 0)
            limit = msec/self.textWait;
    }
    CGImageRef ownImage = [self allocSnapshotAtTime_: (CGFloat)msec/1000.0
                                            textLimit: limit];

    CGContextSaveGState(context);
    // 最終合成
    CGRect rect;
    rect.origin = NSZeroPoint; rect.size = scene.size;
    CGContextSetAlpha(context, alpha);
    CGContextDrawImage(context, rect, ownImage);
    CFRelease(ownImage);
    CGContextRestoreGState(context);
}

- (void)renderInContext:(CGContextRef)context
{
    NSAssert(scene != nil, @"Layer's scene property cannot be nil.");

    CGContextSaveGState(context);

    // postEffectからのCGImageはキャッシュ化できないらしい
    if(effector == nil && postEffect == nil)
    {
        // キャッシュモード
        if(cache == nil)
        {
            // 静的配置は常に同じなのでキャッシュを使う
            cache = _CGLayerCreateForCache(context, scene.size);        
            {
                CGContextRef cacheContext = CGLayerGetContext(cache);
                
                CGRect rect;
                rect.origin = NSZeroPoint; rect.size = scene.size;
                CGImageRef ownImage = [self allocSnapshotAtTime_: 0
                                                        textLimit: LONG_MAX];
                CGContextDrawImage(cacheContext, rect, ownImage);
                CFRelease(ownImage);
                CGContextFlush(cacheContext);
            }
        }
        
        CGContextSetAlpha(context, alpha); // alphaはキャッシュ外で最後に評価！
        // キャッシュを実出力
        CGContextDrawLayerAtPoint(context, NSZeroPoint, cache);
    }
    else
    {
        // ダイレクトモード
        CGRect rect;
        rect.origin = NSZeroPoint; rect.size = scene.size;
        CGImageRef ownImage = [self allocSnapshotAtTime_: 0
                                                textLimit: LONG_MAX];
        CGContextSetAlpha(context, alpha);
        CGContextDrawImage(context, rect, ownImage);
        CFRelease(ownImage);
    }
    
    CGContextRestoreGState(context);
}

- (void)renderThumbnailInContext:(CGContextRef)context
{
    NSAssert(scene != nil, @"Layer's scene property cannot be nil.");

    CGContextSaveGState(context);

    // キャッシュを経由するとサムネイルの品質が良くないため、直接描画を行う
    CGRect rect;
    rect.origin = NSZeroPoint; rect.size = scene.size;
    CGImageRef ownImage = [self allocSnapshotAtTime_: 0
                                            textLimit: LONG_MAX];
    CGContextDrawImage(context, rect, ownImage);
    CFRelease(ownImage);
    
    CGContextRestoreGState(context);
}

#pragma mark Operator
- (void)cleanupRendering
{
    _clear_cache(self);
    _clear_setter(self);
    [effector release]; effector = nil;
}

- (void)drawTextFrame
{
    NSRect rect = NSRectFromCGRect(textFrame);
    rect.origin.x += origin.x;
    rect.origin.y += origin.y;
    rect = NSInsetRect(rect, 0.5, 0.5);
    id bp = [NSBezierPath bezierPathWithRect: rect];
    [[NSColor orangeColor] set];
    
    [bp stroke];    
}

#pragma mark -
- (QCRenderer*)attachRenderer:(QCComposition*)composition
{
    if(effector)
        [effector release];
    if(composition == nil)
    {
        effector = nil;
        return nil;
    }
    effector = [[QCRenderer alloc] initOffScreenWithSize: scene.size
                                              colorSpace: nil
                                             composition: composition];
    return effector;
}

- (BOOL)isUseEmbeddedComposition
{
    return NO;
}

- (void)forceEffectParameters
{
    _clear_cache(self);
}

- (ECQCCompositionItem*)currentRendererParameters
{
    return [ECQCCompositionItem itemWithRenderer: effector];
}

@end

#pragma mark -
@implementation ECRegularLayer (Internal)
- (NSAttributedString*)_attributedString
{
    id attr;
    if(isVertical)
        attr = [NSDictionary dictionaryWithObjectsAndKeys:
                font, NSFontAttributeName,
                kCFBooleanTrue,kCTVerticalFormsAttributeName,
                nil];
    else
        attr = [NSDictionary dictionaryWithObjectsAndKeys:
                font, NSFontAttributeName,
                nil];
    
    return [[[NSAttributedString alloc]
             initWithString: string
             attributes: attr] autorelease];
}

#pragma mark Render Primitive
- (void)_compositeImage:(CGContextRef)aContext
{
    if(imageRef == nil)
        return;
    
    CGRect rect;
    rect.size = size;
    rect.origin = NSZeroPoint;
    CGContextDrawImage(aContext, rect, [imageRef image]);
}

- (void)_compositeText:(CGContextRef)aContext
                 limit:(CFIndex)textLimit
{
    if(textLimit == 0)
        return ;
    CFIndex limit = MIN(textLimit, [string length]);
    
    CGContextSetShouldSmoothFonts(aContext, true);
    if(typesetter == nil)
        typesetter = CTTypesetterCreateWithAttributedString((CFAttributedStringRef)[self _attributedString]);
    
    if(isVertical)
        [self _compositeVerticalText: aContext
                               limit: limit];
    else
        [self _compositeHorizontalText: aContext
                                 limit: limit];
}

- (void)_compositeVerticalText:(CGContextRef)aContext
                         limit:(CFIndex)limit
{
    CFMutableArrayRef lines = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
    
    CFIndex start = 0;
    CGFloat remainWidth = NSWidth(textFrame);
    CGFloat height = NSHeight(textFrame);
    
    for(;;)
    {
        // 行の決定
        CFIndex br = CTTypesetterSuggestLineBreak(typesetter, start, height);
        CFIndex end = start+br;
        if(end > limit)
            br = limit-start;
        CFRange lineRange = CFRangeMake(start,br);
        CTLineRef line = CTTypesetterCreateLine(typesetter, lineRange);
        
        // 追い出しの判定
        CGFloat as,ds,leading;
        CTLineGetTypographicBounds(line, &as, &ds, &leading);
        
        // 残りフレーム幅を越えたら停止
        if(as+ds>remainWidth)
            break;
        
        // 一行追加
        remainWidth -= as+ds+leading+lineSpacing;
        CFArrayAppendValue(lines, line);
        CFRelease(line);
        
        // レイアウト完了
        start += br;
        if(start == limit)
            break;
    }
    
    // contextの設定
    CGContextSetTextMatrix(aContext, CGAffineTransformIdentity);
    CGFontRef cgfont = CTFontCopyGraphicsFont((CTFontRef)font, nil);
    CGContextSetFont(aContext, cgfont);
    CGFontRelease(cgfont);

    CGContextSetFillColorWithColor(aContext, fontColor);
    CGContextSetFontSize(aContext, [font pointSize]);
    CGContextSetAllowsFontSmoothing(aContext, true);
        
    // version 1.1 フォント装飾を追加
    if(useTextShadow)
        CGContextSetShadowWithColor(aContext,CGSizeMake(1,-1), 3, CGColorGetConstantColor(kCGColorBlack));

    
    // 行座標の設定
    CGFloat x = NSMaxX(textFrame),y;
    CFIndex lineCount = CFArrayGetCount(lines);
    for(CFIndex i=0;i<lineCount;i++)
    {
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        
        CGFloat as,ds,leading,lineWidth;
        lineWidth = CTLineGetTypographicBounds(line, &as, &ds, &leading);
        x -= as; // baseline = top-as
        switch(alignment)
        {
            case NSRightTextAlignment:
                y = lineWidth;
                break;
            case NSCenterTextAlignment:
                y = NSMidY(textFrame)+lineWidth/2.0;
                break;
            case NSLeftTextAlignment:
            default: // それ以外はleft扱いとする
                y = NSMaxY(textFrame);
        }
        
        CGContextSetTextPosition(aContext, x, y);
        x -= ds+leading+lineSpacing;  // 次のtopへ移動
        
        // 各ランに描画命令
        CFArrayRef runs = CTLineGetGlyphRuns(line);
        CFIndex count = CFArrayGetCount(runs);
        
        // V1.1:フォント装飾を追加
        if(useTextStroke)
        {
            CGContextSetStrokeColorWithColor(aContext, textStrokeColor);
            CGContextSetTextDrawingMode(aContext, kCGTextStroke);
            CGContextSetLineWidth(aContext, 2.0);
            CFArrayApplyFunction(runs, CFRangeMake(0,count), (CFArrayApplierFunction)_drawRun, aContext);
        }
        
        CGContextSetTextDrawingMode(aContext, kCGTextFill);
        CFArrayApplyFunction(runs, CFRangeMake(0,count), (CFArrayApplierFunction)_drawRun, aContext);
    }
    
    // 後始末
    CFRelease(lines);
}

- (void)_compositeHorizontalText:(CGContextRef)aContext
                           limit:(CFIndex)limit
{
    CFMutableArrayRef lines = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
    
    CFIndex start = 0;
    CGFloat remainHeight = NSHeight(textFrame);
    CGFloat width = NSWidth(textFrame);
    
    for(;;)
    {
        // 行の決定
        CFIndex br = CTTypesetterSuggestLineBreak(typesetter, start, width);
        CFIndex end = start+br;
        if(end > limit)
            br = limit-start;
        CFRange lineRange = CFRangeMake(start,br);
        CTLineRef line = CTTypesetterCreateLine(typesetter, lineRange);
        
        // 追い出しの判定
        CGFloat as,ds,leading;
        CTLineGetTypographicBounds(line, &as, &ds, &leading);
        
        // 残りフレーム高を越えたら停止
        if(as+ds>remainHeight)
            break;
        
        // 一行追加
        remainHeight -= as+ds+leading+lineSpacing;
        CFArrayAppendValue(lines, line);
        CFRelease(line);
        
        // レイアウト完了
        start += br;
        if(start == limit)
            break;
    }
    
    // contextの設定
    CGContextSetTextMatrix(aContext, CGAffineTransformIdentity);
    CGFontRef cgfont = CTFontCopyGraphicsFont((CTFontRef)font, nil);
    CGContextSetFont(aContext, cgfont);
    CFRelease(cgfont);

    CGContextSetFillColorWithColor(aContext, fontColor);
    CGContextSetFontSize(aContext, [font pointSize]);
    CGContextSetAllowsFontSmoothing(aContext, true);
    
    // V1.1:フォント装飾を追加
    if(useTextShadow)
        CGContextSetShadowWithColor(aContext,CGSizeMake(1,-1), 3, CGColorGetConstantColor(kCGColorBlack));
    
    
    // 行座標の設定
    CGFloat x,y = NSMaxY(textFrame);
    CFIndex lineCount = CFArrayGetCount(lines);
    for(CFIndex i=0;i<lineCount;i++)
    {
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        
        CGFloat as,ds,leading,lineWidth;
        lineWidth = CTLineGetTypographicBounds(line, &as, &ds, &leading);
        y -= as; // baseline = top-as
        switch(alignment)
        {
            case NSRightTextAlignment:
                x = NSMaxX(textFrame)-lineWidth;
                break;
            case NSCenterTextAlignment:
                x = NSMidX(textFrame)-lineWidth/2.0;
                break;
            case NSLeftTextAlignment:
            default: // それ以外はleft扱いとする
                x = NSMinX(textFrame);
        }
        
        CGContextSetTextPosition(aContext, x, y);
        y -= ds+leading+lineSpacing; // 次のtopへ移動
        
        // 各ランに描画命令
        CFArrayRef runs = CTLineGetGlyphRuns(line);
        CFIndex count = CFArrayGetCount(runs);
        if(useTextStroke)
        {
            CGContextSetStrokeColorWithColor(aContext, textStrokeColor);
            CGContextSetTextDrawingMode(aContext, kCGTextStroke);
            CGContextSetLineWidth(aContext, 2.0);
            CFArrayApplyFunction(runs, CFRangeMake(0,count), (CFArrayApplierFunction)_drawRun, aContext);
        }
        CGContextSetTextDrawingMode(aContext, kCGTextFill);
        CFArrayApplyFunction(runs, CFRangeMake(0,count), (CFArrayApplierFunction)_drawRun, aContext);
    }
    
    // 後始末
    CFRelease(lines);
}

// alpha抜きの評価を行う
// CGImage上で合成を掛けないと、サブピクセルのアンチエイリアスなどでイメージが変わってしまう
- (CGImageRef)allocSnapshotAtTime_:(CGFloat)duration
                          textLimit:(CFIndex)limit
{
    // 最初に全体をCGImageで合成する
    CGContextRef imgContext = _CGContextCreateForComposition(scene.size);
    
    // イメージが大きすぎる場合は拒否される
    if(imgContext == nil)
        return nil;
    
    // ポジションはCTMで補正
    CGAffineTransform trans = CGAffineTransformMakeTranslation(origin.x, origin.y);
    CGContextConcatCTM(imgContext, trans);

    // イメージを配置
    if(imageRef)
        [self _compositeImage: imgContext];
    
    // テキストを配置
    if(string)
        [self _compositeText: imgContext
                       limit: limit];
    
    CGImageRef baseImage = CGBitmapContextCreateImage(imgContext);
    CFRelease(imgContext);
    
    // 基本イメージが確定
    CGImageRef finalImage = baseImage;
    
    // ポストエフェクトの判定
    if(postEffect)
    {
        if(!effector)
        {
            id composition = [postEffect loadComposition];
            effector = [[QCRenderer alloc] initOffScreenWithSize: scene.size
                                                      colorSpace: nil
                                                     composition: composition];
            [postEffect apply: effector];
        }
    }

    if(effector)
    {
        // final imageを置換え
        [effector setValue: (id)baseImage forInputKey: QCCompositionInputImageKey];
        if([effector renderAtTime: duration arguments: nil])
        {
            finalImage = (CGImageRef)[effector createSnapshotImageOfType: @"CGImage"];
            CFRelease(baseImage);
            [effector setValue: nil forInputKey: QCCompositionInputImageKey];
        }
    }

    return finalImage;
}

@end

