美文网首页
CoreText Attribute String 中Font

CoreText Attribute String 中Font

作者: 剁椒鸡蛋zy | 来源:发表于2018-04-23 12:29 被阅读0次

做图文混排离不开CoreText,大体思路是 把图片用空的占位字符\uFFFC替换,保存图片的宽高,然后设置对应AttributeStringCTRunDelegateRefCTFrameDraw或者CTLineDraw的时候就会有留出图片的空间,然后通过CGContextDrawImage或者[UIView addSubView]等方式 把图片显示出来,代码如下

- (NSDictionary *)p_addAttri:(int)i{
    CGFloat pSpacing = 0;
    pSpacing = roundf(0);
    
    NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
    CTLineBreakMode lineBreak = kCTLineBreakByCharWrapping;
    
    UIFont *f = [UIFont systemFontOfSize:20];
    CFStringRef fontName = (__bridge CFStringRef)f.fontName;
    CGFloat fontSize     = f.pointSize;
    CTFontRef ctfont     = CTFontCreateWithName(fontName, fontSize, NULL);
    [attributes setObject:(__bridge id)ctfont forKey:(id)kCTFontAttributeName];
    
    CTParagraphStyleSetting setting[] = {
        { kCTParagraphStyleSpecifierParagraphSpacing,   sizeof(pSpacing), &pSpacing },
         { kCTParagraphStyleSpecifierLineBreakMode,      sizeof(CTLineBreakMode), &lineBreak }
    };
    CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(setting, sizeof(setting) / sizeof(CTParagraphStyleSetting));
    [attributes setObject:(__bridge id)paragraphStyle forKey:NSParagraphStyleAttributeName];
    // 注意这里要释放paragraphStyle 因为 addAttribute方法会Retain 一下
    
    GWWTextAttachmentCallbackModel *model = [GWWTextAttachmentCallbackModel new];
    if (i == 1) {
        model.size = CGSizeMake(360, 20);
    }else{
        model.size = CGSizeMake(360, 100);
    }
    model.originalString = @"\uFFFC";
    
    CTRunDelegateCallbacks callbacks = model.callbacks;
    CTRunDelegateRef runDelegate = CTRunDelegateCreate(&callbacks, (__bridge_retained void *)model);
    [attributes setObject:(__bridge id _Nonnull)(runDelegate) forKey:(NSString *)kCTRunDelegateAttributeName];
    CFRelease(paragraphStyle);
    CFRelease(runDelegate);
    return attributes;
}


    NSMutableAttributedString *atrriStr = [[NSMutableAttributedString alloc] initWithString:@"\uFFFC\uFFFC\uFFFC"];
    [atrriStr addAttributes:[self p_addAttri:0] range:NSMakeRange(0, 1)];
    [atrriStr addAttributes:[self p_addAttri:1] range:NSMakeRange(1, 1)];
    [atrriStr addAttributes:[self p_addAttri:2] range:NSMakeRange(2, 1)];

static CGFloat RunDelegateGetAscentCallback(void *refCon){
    GWWTextAttachmentCallbackModel *object = (__bridge GWWTextAttachmentCallbackModel *)refCon;
    return object.size.height;
}

static CGFloat RunDelegateGetDescentCallback(void *refCon){
    return 0.0f;
}

static CGFloat RunDelegateGetWidthCallback(void *refCon){
    GWWTextAttachmentCallbackModel *object = (__bridge GWWTextAttachmentCallbackModel *)refCon;
    return object.size.width;
}

上面的代码中GWWTextAttachmentCallbackModel是一个简单的模型类,保存图片宽高,方便存取。


    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)atrriStr);
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL,rect);
    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, atrriStr.length), path,nil);

    CFArrayRef lines = CTFrameGetLines(frame);
    CFIndex count = CFArrayGetCount(lines);
    CGPoint origin[count];
    CTFrameGetLineOrigins(frame, CFRangeMake(0, 0),origin);
    
    // first line
    CGFloat ascent,descent,leading,width = 0;
    CTLineRef line1 = CFArrayGetValueAtIndex(lines,0);
    width = CTLineGetTypographicBounds(line1, &ascent, &descent, &leading);
    CGPoint line1Pos = origin[0];
    CGRect line1Rect =  CGRectMake(line1Pos.x, line1Pos.y - descent, width, ascent + descent);
    CGRect baseLine1 =  CGRectMake(line1Pos.x, line1Pos.y, width,0.1);
    
    // second line
    CTLineRef line2 = CFArrayGetValueAtIndex(lines,1);
    width = CTLineGetTypographicBounds(line2, &ascent, &descent, &leading);
    CGPoint line2Pos = origin[1];
    CGRect line2Rect =  CGRectMake(line2Pos.x, line2Pos.y - descent, width, ascent + descent);
    CGRect baseLine2 =  CGRectMake(line2Pos.x, line2Pos.y, width,0.1);

    // last line
    CTLineRef lastLine = CFArrayGetValueAtIndex(lines, count - 1);
    width = CTLineGetTypographicBounds(lastLine, &ascent, &descent, &leading);
    CGPoint lastLinePos = origin[count - 1];
    CGRect lastLineRect =  CGRectMake(lastLinePos.x, lastLinePos.y - descent, width, ascent + descent);
    CGRect baseLine =  CGRectMake(lastLinePos.x, lastLinePos.y, width,0.1);
    NSLog(@"lastlast rect= %@",NSStringFromCGRect(lastLineRect));
    CFRange lastLinerange = CTLineGetStringRange(lastLine);
    NSRange nsLastLineRange = NSMakeRange(lastLinerange.location, lastLinerange.length);

上面的代码是计算每行显示的rect,baseline 的rect。

    
    UIImage *img = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"img.jpeg" ofType:nil]];
    CGContextDrawImage(ctx, line1Rect, img.CGImage);
    CGContextDrawImage(ctx, lastLineRect, img.CGImage);

绘制图片的代码。


image.png

如图,红色矩形区域表示每一行的区域,就是图片占据的区域,蓝色的线表示的是每行的baseLine,CoreText在进行文本绘制的时候以baseLine为参看绘制文字的,每个蓝色线到对应矩形顶部的高度是ascent,到底部的是descent。
如上图可以发现,如果我们图片宽高是 300 * 50 的话,但是实际区域是300 * 55 高度多出来了5,其实这个5的高度就是descent。

问题就是如何让5 变成0?

其实 descent 和当前attribute string 中font 属性相关,font size 约到,descent 就越大,我们把font size = 1,descent 就会很小, 可以忽略了。

UIFont *f = [UIFont systemFontOfSize:1];
image.png

看一下下面这个问题

UIFont *f = [UIFont systemFontOfSize:20];
    if (i == 1) {
        model.size = CGSizeMake(360, 5);
    }else{
        model.size = CGSizeMake(360, 100);
    }

字号 设为20 , 其中第二行的高度设置为5后 效果如下图:


image.png

可以看到第一行和第二行中间竟然不是连续的而是空出了一部分区域,为啥呢?

跟踪一下具体代码,发现 第一行的baseLine 距离第二行的baseLine 间距大概24,每行的descent 为4 , 24-4 = 20 正好是字体的大小。

由此得出,CoreText 在计算的时候主要是参看每行attributestring中font 属性的值,如果font 高度为20 ,CoreText在计算的时候就至少会留出20的高度。

当把字体设为1,其他什么都不改 如图


image.png

从上面的案例 可以看出 CoreText 计算布局的时候每行 Attribute String的Font 会决定两行之间最小的间距,决定每行descent的大小。同时没有设置font 的时候,系统会默认使用szie=12的font。

image.png

示例代码 https://github.com/githubdelegate/CoreTextTest.git

相关文章

网友评论

      本文标题:CoreText Attribute String 中Font

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