如何在调用CTRunDelegate回调时获取像素坐标

时间:2014-05-19 05:03:56

标签: objective-c core-text emoticons

我将动态文本绘制到自定义UIImageView中。文本可以包含像:-)或;-)这样的字符组合,我想用PNG图像替换它。

我为下面的一堆代码道歉。

创建CTRunDelegate的代码如下:

CTRunDelegateCallbacks callbacks;
callbacks.version = kCTRunDelegateVersion1;
callbacks.dealloc = emoticonDeallocationCallback;
callbacks.getAscent = emoticonGetAscentCallback;
callbacks.getDescent = emoticonGetDescentCallback;
callbacks.getWidth = emoticonGetWidthCallback;
// Functions: emoticonDeallocationCallback, emoticonGetAscentCallback, emoticonGetDescentCallback, emoticonGetWidthCallback are properly defined callback functions

CTRunDelegateRef ctrun_delegate = CTRunDelegateCreate(&callbacks, self);
// self is what delegate will be using as void*refCon parameter

创建属性字符串的代码是:

NSMutableAttributedString* attString = [[NSMutableAttributedString alloc] initWithString:self.data attributes:attrs];
// self.data is string containing text
// attrs is just setting for font type and color

然后我将CTRunDelegate添加到此字符串中:

CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attString, range, kCTRunDelegateAttributeName, ctrun_delegate);
// where range is for one single emoticon location in text (eg. location=5, length = 2)
// ctrun_delegate is previously created delegate for certain type of emoticon

回调函数定义如下:

void emoticonDeallocationCallback(void*refCon)
{
    // dealloc code goes here
}
CGFloat emoticonGetAscentCallback(void * refCon)
{
    return 10.0;
}
CGFloat emoticonGetDescentCallback(void * refCon)
{
    return 4.0;
}
CGFloat emoticonGetWidthCallback(void * refCon)
{
    return 30.0;
}

现在这一切都运行正常 - 我调用了回调函数,我可以看到宽度,上升和下降会影响检测到“emoticon char combo”之前和之后的文本。

现在我想在制作这个“洞”的地方画一个图像,但是我找不到任何可以指导我如何在每个回调中获得像素(或其他一些)坐标的文档。

任何人都可以指导我如何阅读这些内容吗?

提前致谢!

P.S。

据我所见,调用CTFramesetterCreateWithAttributedString时会调用回调函数。所以基本上还没有画画。我找不到任何显示如何将表情符号位置与绘制文本中的位置匹配的示例。可以吗?

2 个答案:

答案 0 :(得分:2)

我找到了解决方案!

回顾一下:问题是使用CoreText将文本绘制到UIImageView中,除了明显的字体类型和颜色格式之外,此文本需要将文本的一部分替换为小图像,插入替换子文本的位置(例如。:-)将成为一个笑脸)。

以下是:

1)为所有支持的表情符号搜索提供的字符串(例如,搜索:-) substring)

NSRange found = [self.rawtext rangeOfString:emoticonString options:NSCaseInsensitiveSearch range:searchRange];

如果发现事件,请将其存储在CFRange:

CFRange cf_found = CFRangeMake(found.location, found.length);

如果您正在搜索多个不同的表情符号(例如:) :-) ;-);)等),请按照其位置的升序对所有找到的事件进行排序。

2)替换所有表情符号子串(例如:-)),你想用一个空的空间替换图像。在此之后,您还必须更新找到的位置以匹配这些新空格。它并不像听起来那么复杂。

3)对每个表情符号使用CTRunDelegateCreate将回调添加到新创建的字符串(没有:-),而不是[SPACE])。

4)回调函数显然应根据您将使用的图像大小返回正确的表情符号宽度。

5)一旦你执行CTFramesetterCreateWithAttributedString,这些回调也将被执行,给出framesetter数据,稍后将用于创建给定帧路径中绘制的字形。

6)现在我错过了一部分:一旦你使用CTFramesetterCreateFrame为framesetter创建框架,循环浏览所有找到的表情符号并执行以下操作:

从帧中获取num行并获取第一行的原点:

CFArrayRef lines = CTFrameGetLines(frame);
int linenum = CFArrayGetCount(lines);

CGPoint origins[linenum];
CTFrameGetLineOrigins(frame, CFRangeMake(0, linenum), origins);

遍历所有行,为每个表情符号寻找包含它的字形(基于每个表情符号的range.location和每个字形中的字符数):

(灵感来自这里:CTRunGetImageBounds returning inaccurate results

int eloc = emoticon.range.location; // emoticon's location in text

for( int i = 0; i<linenum; i++ )
{
    CTLineRef line = (CTLineRef)CFArrayGetValueAtIndex(lines, i);
    CFArrayRef gruns = CTLineGetGlyphRuns(line);
    int grunnum = CFArrayGetCount(gruns);
    for( int j = 0; j<grunnum; j++ )
    {
        CTRunRef grun = (CTRunRef) CFArrayGetValueAtIndex(gruns, j);

        int glyphnum = CTRunGetGlyphCount(grun);
        if( eloc > glyphnum )
        {
            eloc -= glyphnum;
        }
        else
        {
            CFRange runRange = CTRunGetStringRange(grun);
            CGRect runBounds;
            CGFloat ascent,descent;
            runBounds.size.width = CTRunGetTypographicBounds(grun, CFRangeMake(0, 0), &ascent, &descent, NULL);
            runBounds.size.height = ascent + descent;

            CGFloat xOffset = CTLineGetOffsetForStringIndex(line, runRange.location, NULL);

            runBounds.origin.x = origins[i].x + xOffset;
            runBounds.origin.y = origins[i].y;
            runBounds.origin.y -= descent;

            emoticon.location = CGPointMake(runBounds.origin.x + runBounds.size.width, runBounds.origin.y);

            emoticon.size = CGPointMake([emoticon EmoticonWidth] ,runBounds.size.height);

            break;
        }
    }
}

请不要将此代码作为复制 - 粘贴和将要工作,因为我必须删除许多其他东西 - 所以这只是为了解释我做了什么,而不是按原样使用它。

7)最后,我可以创建上下文并在正确的位置绘制文本和表情符号:

if(currentContext)
{
    CGContextSaveGState(currentContext);
    {
        CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
        CTFrameDraw(frame, currentContext);
    }
    CGContextRestoreGState(currentContext);

    if( foundEmoticons != nil )
    {
        for( FoundEmoticon *emoticon in foundEmoticons )
        {
            [emoticon DrawInContext:currentContext];
        }
    }
}

绘制表情符号的功能(我只是绘制它的边框和轴心点):

-(void) DrawInContext:(CGContext*)currentContext
{
    CGFloat R = round(10.0 * [self randomFloat] ) * 0.1;
    CGFloat G = round(10.0 * [self randomFloat] ) * 0.1;
    CGFloat B = round(10.0 * [self randomFloat] ) * 0.1;
    CGContextSetRGBStrokeColor(currentContext,R,G,B,1.0);

    CGFloat pivotSize = 8.0;

    CGContextBeginPath(currentContext);
    CGContextMoveToPoint(currentContext, self.location.x, self.location.y - pivotSize);
    CGContextAddLineToPoint(currentContext, self.location.x, self.location.y + pivotSize);
    CGContextMoveToPoint(currentContext, self.location.x - pivotSize, self.location.y);
    CGContextAddLineToPoint(currentContext, self.location.x + pivotSize, self.location.y);
    CGContextDrawPath(currentContext, kCGPathStroke);

    CGContextBeginPath(currentContext);
    CGContextMoveToPoint(currentContext, self.location.x, self.location.y);
    CGContextAddLineToPoint(currentContext, self.location.x + self.size.x, self.location.y);
    CGContextAddLineToPoint(currentContext, self.location.x + self.size.x, self.location.y + self.size.y);
    CGContextAddLineToPoint(currentContext, self.location.x, self.location.y + self.size.y);
    CGContextAddLineToPoint(currentContext, self.location.x, self.location.y);
    CGContextDrawPath(currentContext, kCGPathStroke);
}

结果图片:http://i57.tinypic.com/rigis5.png

: - )))

P.S。

以下是包含多行的结果图片:http://i61.tinypic.com/2pyce83.png

P.P.S。

这是具有多行的结果图像和用于表情符号的PNG图像: http://i61.tinypic.com/23ixr1y.png

答案 1 :(得分:1)

您是在UITextView对象中绘制文字吗?如果是这样,那么你可以询问它的布局管理器绘制表情符号的位置,特别是-[NSLayoutManager boundingRectForGlyphRange:inTextContainer:方法(也可以获取文本视图的文本容器)。

请注意,它需要字形范围,而不是字符范围。多个字符可以组成一个字形,因此您需要在它们之间进行转换。同样,NSLayoutManager具有在字符范围和字形范围之间进行转换的方法。

或者,如果您没有在文本视图中绘图,则应创建自己的布局管理器和文本容器,这样您就可以这样做。

文本容器描述了屏幕上将绘制文本的区域,通常它是一个矩形,但它可以是任何形状:

enter image description here

布局管理器计算出如何使文本适合文本容器描述的任何形状。

这让我想到了你可以采取的另一种方法。您可以修改文本容器对象,添加无法呈现文本的空白区域,并在该空白区域中放置UIImageView。使用布局管理器确定空白区域的位置。

在iOS 7及更高版本中,您可以通过向文本容器中添加“排除路径”来实现此目的,文本容器只是每个图像所在的路径数组(可能是矩形)。对于早期版本的iOS,您需要子类化NSTextContainer并覆盖lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect: