如何使用Core Text和Attributed String伪造上标和下标?

jow*_*wie 11 objective-c subscript nsattributedstring ipad core-text

我正在使用a NSMutableAttribtuedString来构建带有格式的字符串,然后我将其传递给Core Text以渲染到框架中.问题是,我需要使用上标和下标.除非这些字符在字体中可用(大多数字体不支持它),否则设置属性kCTSuperscriptAttributeName什么都不做.

所以我想我只留下了唯一的选择,即通过改变字体大小和移动基线来伪造它.我可以做字体大小位,但不知道改变基线的代码.有人可以帮忙吗?

谢谢!

编辑:我正在考虑,考虑到我可以用来解决这个问题的时间,编辑一个字体,以便给它一个下标"2"......或者找到一个内置的iPad字体.有没有人知道任何带有下标"2"的衬线字体我可以使用?

Tom*_*mmy 14

CTParagraphStyleSpecifiers或定义的字符串属性名称常量中没有基线设置.因此,我认为结论CoreText本身不支持文本的基线调整属性是安全的.在CTTypesetter中有一个对基线位置的引用,但我无法将其与任何能够在iPad的CoreText中的一行中改变基线的能力联系起来.

因此,您可能需要自己干预渲染过程.例如:

  • 创建一个CTFramesetter,例如通过 CTFramesetterCreateWithAttributedString
  • 从那个通道获取CTFrame CTFramesetterCreateFrame
  • 使用CTFrameGetLineOriginsCTFrameGetLines获取CTLines数组以及它们应该被绘制的位置(即,具有适当段落/换行符的文本以及所有其他字距调整/前导/其他定位文本属性应用)
  • 从那些,对于没有上标或下标的行,只需使用CTLineDraw并忘记它
  • 对于那些带有上标或下标的人,用于CTLineGetGlyphRuns获取描述线上各种字形的CTRun对象数组
  • 在每次运行中,用于CTRunGetStringIndices确定运行中的源字符; 如果你想要上标或下标都不包括在内,那就用CTRunDraw它画画
  • 否则,用于CTRunGetGlyphs打破单个字形的运行,并CTRunGetPositions找出它们在正常运行中的绘制位置
  • CGContextShowGlyphsAtPoint适当地使用,在上标或下标中调整你想要的文本矩阵

我还没有找到一种方法来查询字体是否具有自动上标/下标生成的相关提示,这使得事情有点棘手.如果你是绝望的并且没有解决方案,那么根本不使用CoreText的东西可能更容易 - 在这种情况下你应该定义你自己的属性(这就是为什么[NS/CF] AttributedString允许任意属性应用,由字符串名称标识)并使用常规的NSString搜索方法来识别需要在上标或下标中打印的区域.

出于性能原因,二进制搜索可能是继续搜索所有行,一行中的运行以及运行中的字形以供您感兴趣的方式.假设您有一个自定义UIView子类来绘制CoreText内容,它可能是更聪明地提前做,而不是每个drawRect :(或等效的方法,如果你使用CATiledLayer).

此外,CTRun方法的变体请求指向C数组的指针,该数组包含您要求复制的内容,可能会保存复制操作但不一定成功.查看文档.我刚刚确定我正在草拟一个可行的解决方案,而不是通过CoreText API绘制绝对最佳的路线.

  • 最后我意识到我们可以只使用UTF-8字符并将其放入文本文件中...我试图在Core Text中使用Subscript特性而它只是不起作用,但是将下标设为'2'作为字符串中的UTF-8字符工作正常.奇怪的! (2认同)

Dim*_*iol 5

这是一些基于Tommy概述的代码,可以很好地完成工作(尽管仅在单行上进行了测试)。使用设置属性字符串的基线@"MDBaselineAdjust",此代码将线绘制到offseta CGPoint。要获得上标,还可以将字体大小降低一个缺口。可能的预览:http : //cloud.mochidev.com/IfPF(读为“ [Xe] 4f 14 ...”的行)

希望这可以帮助 :)

NSAttributedString *string = ...;
CGPoint origin = ...;

CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)string);
CGSize suggestedSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, string.length), NULL, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX), NULL);
CGPathRef path = CGPathCreateWithRect(CGRectMake(origin.x, origin.y, suggestedSize.width, suggestedSize.height), NULL);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, string.length), path, NULL);
NSArray *lines = (NSArray *)CTFrameGetLines(frame);
if (lines.count) {
    CGPoint *lineOrigins = malloc(lines.count * sizeof(CGPoint));
    CTFrameGetLineOrigins(frame, CFRangeMake(0, lines.count), lineOrigins);

    int i = 0;
    for (id aLine in lines) {
        NSArray *glyphRuns = (NSArray *)CTLineGetGlyphRuns((CTLineRef)aLine);

        CGFloat width = origin.x+lineOrigins[i].x-lineOrigins[0].x;

        for (id run in glyphRuns) {
            CFRange range = CTRunGetStringRange((CTRunRef)run);
            NSDictionary *dict = [string attributesAtIndex:range.location effectiveRange:NULL];
            CGFloat baselineAdjust = [[dict objectForKey:@"MDBaselineAdjust"] doubleValue];

            CGContextSetTextPosition(context, width, origin.y+baselineAdjust);

            CTRunDraw((CTRunRef)run, context, CFRangeMake(0, 0));
        }

        i++;
    }

    free(lineOrigins);
}
CFRelease(frame);
CGPathRelease(path);
CFRelease(framesetter);
Run Code Online (Sandbox Code Playgroud)

`