获取在iOS UIFont中跟踪字符的路径

23
假设我有一个自定义字体“Foo”,我正在我的iOS应用程序中使用它。 我已将其添加到项目,plist等中,并且能够完美地呈现UILabel等。
现在,如果我想找出在该字体中“跟踪”字母'P'的一系列点,我该如何获得这个点序列? 例如,假设我想使用CGPath慢慢绘制字母'P',就像绘图仪一样...
我只需要一个CGPoint序列数组,如果作为路径绘制,将绘制一个'P'。
我意识到对于一些字母(如'T'),这可能没有意义,因为必须抬起笔来穿过'T'。 所以也许我需要一系列CGPath ...
如何完成此操作的任何提示?
谢谢!

1
你尝试过什么?(http://whathaveyoutried.com) - EmilioPelaez
3个回答

43
从字母“P”到点序列需要进行几个步骤。您需要使用Core Text。

  1. 创建一个CTFont。自iOS 7以来,您可以在需要CTFont的地方使用UIFont(它们是“toll-free bridged”)。您还可以使用CTFontCreateWithGraphicsFont函数直接从CGFont创建CTFont,或者使用CTFontCreateWithName(或使用其他几种方法)按名称创建字体。
  2. 使用CTFontGetGlyphsForCharacters函数获取该字母的字形。对于字母“P”,应该只有一个字形。对于某些非英语脚本中的字符,您可能会得到多个(组合)字形。
  3. 使用CTFontCreatePathForGlyph函数获取字形的CGPath
  4. 使用CGPathApply枚举路径的元素。
  5. 将路径的每条线、二次曲线和三次曲线元素转换为一系列点。苹果没有提供公共API来执行此操作。您需要自己完成。对于直线元素很容易。对于曲线元素,如果您不知道如何渲染贝塞尔曲线,则需要进行一些研究。例如,请参见将Bezier曲线转换为多边形链?

我们可以在Swift playground中轻松进行实验:

import UIKit
import CoreText
import XCPlayground

let font = UIFont(name: "HelveticaNeue", size: 64)!

var unichars = [UniChar]("P".utf16)
var glyphs = [CGGlyph].init(repeating: 0, count: unichars.count)
let gotGlyphs = CTFontGetGlyphsForCharacters(font, &unichars, &glyphs, unichars.count)
if gotGlyphs {
    let cgpath = CTFontCreatePathForGlyph(font, glyphs[0], nil)!
    let path = UIBezierPath(cgPath: cgpath)
    print(path)
    XCPlaygroundPage.currentPage.captureValue(value: path, withIdentifier: "glyph \(glyphs[0])")
}

结果:

<UIBezierPath: 0x7fbc89e0d370; <MoveTo {11.072000000000001, 23.808}>,
 <LineTo {11.072000000000001, 40.576000000000001}>,
 <LineTo {22.975999999999999, 40.576000000000001}>,
 <QuadCurveTo {30.560000000000002, 38.432000000000002} - {28.16, 40.576000000000001}>,
 <QuadCurveTo {32.960000000000001, 32.192} - {32.960000000000001, 36.288000000000004}>,
 <QuadCurveTo {30.560000000000002, 25.920000000000002} - {32.960000000000001, 28.096}>,
 <QuadCurveTo {22.975999999999999, 23.808} - {28.16, 23.744}>,
 <Close>,
 <MoveTo {4.992, 45.695999999999998}>,
 <LineTo {4.992, 0}>,
 <LineTo {11.072000000000001, 0}>,
 <LineTo {11.072000000000001, 18.687999999999999}>,
 <LineTo {25.024000000000001, 18.687999999999999}>,
 <QuadCurveTo {35.488, 22.208000000000002} - {31.936, 18.623999999999999}>,
 <QuadCurveTo {39.039999999999999, 32.192} - {39.039999999999999, 25.792000000000002}>,
 <QuadCurveTo {35.488, 42.143999999999998} - {39.039999999999999, 38.591999999999999}>,
 <QuadCurveTo {25.024000000000001, 45.695999999999998} - {31.936, 45.695999999999998}>,
 <Close>

P字形路径


@RobMayoff 很棒的回答!正是我也在寻找的!既然您似乎了解您的CGPaths,那么您知道我如何向刚创建的路径添加一条线吗?例如,回到起点。仅仅添加:let pathP = CGPathCreateMutableCopy(CGPathCreateMutableCopy(cgpath)) CGPathAddLineToPoint(pathP, nil, 0, 0)似乎不起作用。 - user594883
“doesn't seem to work” 的意思是什么?您需要发布一个新问题,其中包含您的代码、输出(转储您修改后的路径)以及解释输出有什么问题。 - rob mayoff
@RobMayoff https://dev59.com/JJbfa4cB1Zd3GeqPzNXQ - user594883
我在我的项目中使用了你的代码,但是字母“P”被翻转了。我打印了我的图层的contentsAreFlipped属性,它返回true。我该如何解决这个问题?让线:CAShapeLayer = CAShapeLayer() line.path = path.cgPath line.strokeColor = UIColor.blue.cgColor line.fillColor = UIColor.clear.cgColor line.position = self.view.center self.view.layer.addSublayer(line) - banu
已经修复了,我添加了这行代码。isGeometryFlipped = true。 - banu
我想做相同的事情,但是相反的,我只想将绘制的路径提取为文本/字形。我该怎么做?请帮忙? - MALIKK HABIB UR REHMAN

4

Rob的答案已更新,适用于Swift 4+和CoreGraphics的垂直翻转使用反转坐标系:

    var unichars = [UniChar]("P".utf16)
    var glyphs = [CGGlyph](repeating: 0, count: unichars.count)
    let gotGlyphs = CTFontGetGlyphsForCharacters(font, &unichars, &glyphs, unichars.count)
    if gotGlyphs {
        let cgpath = CTFontCreatePathForGlyph(font, glyphs[0], nil)!
        var inverse = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -cgpath.boundingBox.height - 1)
        let path = UIBezierPath(cgPath: cgpath.copy(using: &inverse)!)
        print(path)

3

对于那些在 Objective C 中寻找相同解决方案的人

    UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:64];
    CGFontRef fontref = CGFontCreateWithFontName((__bridge CFStringRef)font.fontName);
    NSString *unichars = @"I";
    CFStringRef yourFriendlyCFString = (__bridge CFStringRef)unichars;
    CGGlyph glyphs = CGFontGetGlyphWithGlyphName(fontref, yourFriendlyCFString);
    CTFontRef fontCT = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL);
    CGPathRef cgpath = CTFontCreatePathForGlyph(fontCT, glyphs, nil);
    UIBezierPath *path = [UIBezierPath bezierPathWithCGPath:cgpath];
    NSLog(@"Complete path For p is %@", path);
    CGPathApply(cgpath, (__bridge void * _Nullable)(bezierPoints), MyCGPathApplierFunc);
    NSLog(@"Points on path %@", bezierPoints);
    for(NSValue *point in bezierPoints){

       //Do your work
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接