美文网首页
2018-04-26

2018-04-26

作者: 今天lgw | 来源:发表于2018-04-26 11:41 被阅读11次

    ###富文本

    NSDictionary * dic = @{NSFontAttributeName:[UIFont fontWithName:@"Zapfino" size:20],NSForegroundColorAttributeName:[UIColor redColor],NSUnderlineStyleAttributeName:@(NSUnderlineStyleSingle)};
        NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:@"0我是一个富文本,9听说我有很多属性,19I will try。32这里清除属性."];
    //    设置属性
        [attributeStr setAttributes:dic range:NSMakeRange(0, attributeStr.length)];
    //    添加属性
        [attributeStr addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:30] range:NSMakeRange(9, 10)];
        [attributeStr addAttribute:NSForegroundColorAttributeName value:[UIColor cyanColor] range:NSMakeRange(13, 13)];
    //    添加多个属性
        NSDictionary * dicAdd = @{NSBackgroundColorAttributeName:[UIColor yellowColor],NSLigatureAttributeName:@1};
        [attributeStr addAttributes:dicAdd range:NSMakeRange(19, 13)];
    //    移除属性
        [attributeStr removeAttribute:NSFontAttributeName range:NSMakeRange(32, 9)];
        UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 400)];
        label.numberOfLines = 0;
        label.attributedText = attributeStr;
    
    

    CoreText绘制富文本

    -(void)drawRect:(CGRect)rect
    {
        [super drawRect:rect];
    //获取当前绘制上下文
        CGContextRef context = UIGraphicsGetCurrentContext();
    //设置字形的变换矩阵为不做图形变换 
        CGContextSetTextMatrix(context, CGAffineTransformIdentity);  
    //平移方法,将画布向上平移一个屏幕高  
      CGContextTranslateCTM(context, 0, self.bounds.size.height);
    //缩放方法,x轴缩放系数为1,则不变,y轴缩放系数为-1,则相当于以x轴为轴旋转180度
        CGContextScaleCTM(context, 1.0, -1.0);
        NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:@"\n这里在测试图文混排,\n我是一个富文本"];
    /*
     设置一个回调结构体,告诉代理该回调那些方法
     */
    CTRunDelegateCallbacks callBacks;//创建一个回调结构体,设置相关参数
    memset(&callBacks,0,sizeof(CTRunDelegateCallbacks));//memset将已开辟内存空间 callbacks 的首 n 个字节的值设为值 0, 相当于对CTRunDelegateCallbacks内存空间初始化
    callBacks.version = kCTRunDelegateVersion1;//设置回调版本,默认这个
    callBacks.getAscent = ascentCallBacks;//设置图片顶部距离基线的距离
    callBacks.getDescent = descentCallBacks;//设置图片底部距离基线的距离
    callBacks.getWidth = widthCallBacks;//设置图片宽度
    
    
       NSDictionary * dicPic = @{@"height":@129,@"width":@400};//创建一个图片尺寸的字典,初始化代理对象需要
        CTRunDelegateRef delegate = CTRunDelegateCreate(& callBacks, (__bridge void *)dicPic);//创建代理
    
        unichar placeHolder = 0xFFFC;//创建空白字符
        NSString * placeHolderStr = [NSString stringWithCharacters:&placeHolder length:1];//已空白字符生成字符串
        NSMutableAttributedString * placeHolderAttrStr = [[NSMutableAttributedString alloc] initWithString:placeHolderStr];//用字符串初始化占位符的富文本
        CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolderAttrStr, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate);//给字符串中的范围中字符串设置代理
        CFRelease(delegate);//释放(__bridge进行C与OC数据类型的转换,C为非ARC,需要手动管理)
    
    [attributeStr insertAttributedString:placeHolderAttrStr atIndex:12];//将占位符插入原富文本
    
    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributeStr);//一个frame的工厂,负责生成frame
    CGMutablePathRef path = CGPathCreateMutable();//创建绘制区域
    CGPathAddRect(path, NULL, self.bounds);//添加绘制尺寸
    NSInteger length = attributeStr.length;
    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0,length), path, NULL);//工厂根据绘制区域及富文本(可选范围,多次设置)设置frame
    CTFrameDraw(frame, context);//根据frame绘制文字
    
        
        UIImage * image = [UIImage imageNamed:@"bd_logo1"];
        CGRect imgFrm = [self calculateImageRectWithFrame:frame];
        //绘制图片
        CGContextDrawImage(context,imgFrm, image.CGImage);
        CFRelease(frame);
        CFRelease(path);
        CFRelease(frameSetter);
    }
    static CGFloat ascentCallBacks(void * ref)
    {
        return [(NSNumber *)[(__bridge NSDictionary *)ref valueForKey:@"height"] floatValue];
    }
    static CGFloat descentCallBacks(void * ref)
    {
        return 0;
    }
    static CGFloat widthCallBacks(void * ref)
    {
        return [(NSNumber *)[(__bridge NSDictionary *)ref valueForKey:@"width"] floatValue];
    }
    
    
    -(CGRect)calculateImageRectWithFrame:(CTFrameRef)frame
    {
    NSArray * arrLines = (NSArray *)CTFrameGetLines(frame);//根据frame获取需要绘制的线的数组
    NSInteger count = [arrLines count];//获取线的数量
    CGPoint points[count];//建立起点的数组(cgpoint类型为结构体,故用C语言的数组)
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), points);//获取起点
    
    for (int i = 0; i < count; i ++) {//遍历线的数组
            CTLineRef line = (__bridge CTLineRef)arrLines[i];
            NSArray * arrGlyphRun = (NSArray *)CTLineGetGlyphRuns(line);//获取GlyphRun数组(GlyphRun:高效的字符绘制方案)
            for (int j = 0; j < arrGlyphRun.count; j ++) {//遍历CTRun数组
                CTRunRef run = (__bridge CTRunRef)arrGlyphRun[j];//获取CTRun
                NSDictionary * attributes = (NSDictionary *)CTRunGetAttributes(run);//获取CTRun的属性
                CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[attributes valueForKey:(id)kCTRunDelegateAttributeName];//获取代理
                if (delegate == nil) {//非空
                    continue;
                }
                NSDictionary * dic = CTRunDelegateGetRefCon(delegate);//判断代理字典
                if (![dic isKindOfClass:[NSDictionary class]]) {
                    continue;
                }
                CGPoint point = points[i];//获取一个起点
                CGFloat ascent;//获取上距
                CGFloat descent;//获取下距
                CGRect boundsRun;//创建一个frame
                boundsRun.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
                boundsRun.size.height = ascent + descent;//取得高
                CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);//获取x偏移量
                boundsRun.origin.x = point.x + xOffset;//point是行起点位置,加上每个字的偏移量得到每个字的x
                boundsRun.origin.y = point.y - descent;//计算原点
                CGPathRef path = CTFrameGetPath(frame);//获取绘制区域
                CGRect colRect = CGPathGetBoundingBox(path);//获取剪裁区域边框
                CGRect imageBounds = CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);
                return imageBounds;
    
            }
        }
        return CGRectZero;
    }
    
    
    

    coreText 起初是为OSX设计的,而OSX得坐标原点是左下角,y轴正方向朝上。iOS中坐标原点是左上角,y轴正方向向下。
    若不进行坐标转换,则文字从下开始,还是倒着的

    图片.png

    CTFrame

    第一句呢,获取绘制frame中的所有CTLine。CTLine,又不知道了吧,老司机又要无耻的盗图了。

    图片.png

    上面呢,我们能看到一个CTFrame绘制的原理。

    • CTLine 可以看做Core Text绘制中的一行的对象 通过它可以获得当前行的line ascent,line descent ,line leading,还可以获得Line下的所有Glyph Runs
    • CTRun 或者叫做 Glyph Run,是一组共享想相同attributes(属性)的字形的集合体

    一个CTFrame有几个CTLine组成,有几行文字就有几行CTLine。一个CTLine有包含多个CTRun,一个CTRun是所有属性都相同的那部分富文本的绘制单元。所以CTRun是CTFrame的基本绘制单元
    接着说我们的代码。
    为什么我获取的数组需要进行类型转换呢?因为CTFrameGetLines()返回值是CFArrayRef类型的数据。就是一个c的数组类型吧,暂且先这么理解,所以需要转换。

    对对,这呢就是一个CTRun的尺寸图,什么你问CTRun是啥?还没到那呢,后面会详细介绍。
    在这你只要知道,一会我们绘制图片的时候实际上实在一个CTRun中绘制这个图片,那么CTRun绘制的坐标系中,他会以origin点作为原点进行绘制。
    基线为过原点的x轴,ascent即为CTRun顶线距基线的距离,descent即为底线距基线的距离。
    我们绘制图片应该从原点开始绘制,图片的高度及宽度及CTRun的高度及宽度,我们通过代理设置CTRun的尺寸间接设置图片的尺寸。

    图片.png 图片.png
    1、图文混排
    CTFrameRef  textFrame     // coreText 的 frame
    CTLineRef   line          // coreText 的 line
    CTRunRef    run           // line  中的部分文字
    
    2、相关方法:
    CFArrayRef CTFrameGetLines(CTFrameRef frame) //获取包含CTLineRef的数组
    void CTFrameGetLineOrigins(CTFrameRef frame,CFRange range,CGPoint origins[])//获取所有CTLineRef的原点
    CFRange CTLineGetStringRange(CTLineRef line) //获取line中文字在整段文字中的Range
    CFArrayRef CTLineGetGlyphRuns(CTLineRef line)//获取line中包含所有run的数组
    CFRange CTRunGetStringRange(CTRunRef run)//获取run在整段文字中的Range
    CFIndex CTLineGetStringIndexForPosition(CTLineRef line,CGPoint position)//获取点击处position文字在整段文字中的index
    CGFloat CTLineGetOffsetForStringIndex(CTLineRef line,CFIndex charIndex,CGFloat* secondaryOffset)//获取整段文字中charIndex位置的字符相对line的原点的x值
    

    先来了解一下CTFrame内部的CTLine和CTRun。

    在CTFrame内部,是有多个CTLine类组成的,每一个CTLine代表一行,每个CTLine又是由多个CTRun来组成,每一个CTRun代表一组显示风格一致的文本。我们不用手工管理CTLine和CTRun的创建过程。

    CTLine和CTRun示意图如下:


    图片.png

    示意图解释:

    可以看到,第一行的CTLine是由两个CTRun构成的,第一个CTRun为红色大字号的左边部分,第二个CTRun为右边黑色小字号部分。

    虽然我们不用管理CTRun的创建过程,但是我们可以设置某一个具体的CTRun的CTRunDelegate来指定该文本在绘制时的高度、宽度、排列对齐方式等信息。

    对于图片的排版,其实,CoreText本质上是不支持的,但是,可以在显示文本的地方,用一个特殊的空白字符代替,同时设置该字体的CTRunDelegate信息为要显示的图片的宽度和高度信息,这样最后生成的CTFrame实例,就会在绘制时将图片的位置预留出来。以后,在CTDisplayView的drawRect方法中使CGContextDrawImage方法直接绘制出来就行了。

    链接:https://www.jianshu.com/p/6db3289fb05d

    相关文章

      网友评论

          本文标题:2018-04-26

          本文链接:https://www.haomeiwen.com/subject/wukplftx.html