最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - Converting Text to CGPath to svg | Problem with SVG Path Conversion from CGPath in iOS: Circular Path Instead of Text -

programmeradmin1浏览0评论

I'm attempting to convert text to a path using CGPath in iOS and then convert that path to an SVG string, but when I save the SVG and load it again, the path is circular rather than forming the correct text shape.

Here’s my implementation:

textToPath function:

func textToPath(text: String, font: UIFont) -> CGPath? {
    let attributedString = NSAttributedString(string: text, attributes: [.font: font])
    let line = CTLineCreateWithAttributedString(attributedString)
    let runArray = CTLineGetGlyphRuns(line) as NSArray
    
    let path = CGMutablePath()
    
    for run in runArray {
        let run = run as! CTRun
        let count = CTRunGetGlyphCount(run)
        
        for index in 0..<count {
            let range = CFRangeMake(index, 1)
            var glyph: CGGlyph = 0
            var position: CGPoint = .zero
            CTRunGetGlyphs(run, range, &glyph)
            CTRunGetPositions(run, range, &position)
            
            if let glyphPath = CTFontCreatePathForGlyph(font, glyph, nil) {
                var transform = CGAffineTransform(translationX: position.x, y: position.y)
                transform = transform.scaledBy(x: 1, y: -1) // Invert Y-axis to match SVG coordinate system
                
                // Add the glyph path to the main path
                path.addPath(glyphPath, transform: transform)
            }
        }
    }
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
        cgPath = path
    })
    return path
}

pathToSVG function:

func pathToSVG(path: CGPath, color: UIColor) -> String {
    let boundingBox = path.boundingBox
    let svgWidth = boundingBox.width
    let svgHeight = boundingBox.height
    
    var svgString = "<svg width=\"\(svgWidth)\" height=\"\(svgHeight)\" xmlns=\"\">\n"
    svgString += "<path d=\""
    
    path.applyWithBlock { elementPointer in
        let element = elementPointer.pointee
        let points = element.points
        
        switch element.type {
        case .moveToPoint:
            svgString += "M \(points.pointee.x) \(points.pointee.y) "
        case .addLineToPoint:
            svgString += "L \(points.pointee.x) \(points.pointee.y) "
        case .addQuadCurveToPoint:
            svgString += "Q \(points.pointee.x) \(points.pointee.y) \(points.pointee.x) \(points.pointee.y) "
        case .addCurveToPoint:
            svgString += "C \(points.pointee.x) \(points.pointee.y) \(points.pointee.x) \(points.pointee.y) \(points.pointee.x) \(points.pointee.y) "
        case .closeSubpath:
            svgString += "Z "
        @unknown default:
            break
        }
    }
    
    svgString += "\" fill=\"\(color.hexString)\" />\n"
    svgString += "</svg>"
    
    return svgString
}

The problem: When I load the saved SVG file, it creates a circular path instead of the correct text shape. I believe the path conversion is not capturing the correct coordinates or curves, as it doesn't render properly in an SVG viewer.

Additional details: The textToPath function generates a CGPath based on the text and its font. The pathToSVG function is supposed to convert the CGPath to an SVG path string. The saved SVG file displays a circular path rather than the intended text.

What I've tried:

I've checked the transformation applied to the path, and it seems correct for inverting the Y-axis. I tried using both M and L for path commands, but the SVG still does not render the text as expected. Any help would be greatly appreciated. Thanks in advance!

I'm attempting to convert text to a path using CGPath in iOS and then convert that path to an SVG string, but when I save the SVG and load it again, the path is circular rather than forming the correct text shape.

Here’s my implementation:

textToPath function:

func textToPath(text: String, font: UIFont) -> CGPath? {
    let attributedString = NSAttributedString(string: text, attributes: [.font: font])
    let line = CTLineCreateWithAttributedString(attributedString)
    let runArray = CTLineGetGlyphRuns(line) as NSArray
    
    let path = CGMutablePath()
    
    for run in runArray {
        let run = run as! CTRun
        let count = CTRunGetGlyphCount(run)
        
        for index in 0..<count {
            let range = CFRangeMake(index, 1)
            var glyph: CGGlyph = 0
            var position: CGPoint = .zero
            CTRunGetGlyphs(run, range, &glyph)
            CTRunGetPositions(run, range, &position)
            
            if let glyphPath = CTFontCreatePathForGlyph(font, glyph, nil) {
                var transform = CGAffineTransform(translationX: position.x, y: position.y)
                transform = transform.scaledBy(x: 1, y: -1) // Invert Y-axis to match SVG coordinate system
                
                // Add the glyph path to the main path
                path.addPath(glyphPath, transform: transform)
            }
        }
    }
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
        cgPath = path
    })
    return path
}

pathToSVG function:

func pathToSVG(path: CGPath, color: UIColor) -> String {
    let boundingBox = path.boundingBox
    let svgWidth = boundingBox.width
    let svgHeight = boundingBox.height
    
    var svgString = "<svg width=\"\(svgWidth)\" height=\"\(svgHeight)\" xmlns=\"http://www.w3./2000/svg\">\n"
    svgString += "<path d=\""
    
    path.applyWithBlock { elementPointer in
        let element = elementPointer.pointee
        let points = element.points
        
        switch element.type {
        case .moveToPoint:
            svgString += "M \(points.pointee.x) \(points.pointee.y) "
        case .addLineToPoint:
            svgString += "L \(points.pointee.x) \(points.pointee.y) "
        case .addQuadCurveToPoint:
            svgString += "Q \(points.pointee.x) \(points.pointee.y) \(points.pointee.x) \(points.pointee.y) "
        case .addCurveToPoint:
            svgString += "C \(points.pointee.x) \(points.pointee.y) \(points.pointee.x) \(points.pointee.y) \(points.pointee.x) \(points.pointee.y) "
        case .closeSubpath:
            svgString += "Z "
        @unknown default:
            break
        }
    }
    
    svgString += "\" fill=\"\(color.hexString)\" />\n"
    svgString += "</svg>"
    
    return svgString
}

The problem: When I load the saved SVG file, it creates a circular path instead of the correct text shape. I believe the path conversion is not capturing the correct coordinates or curves, as it doesn't render properly in an SVG viewer.

Additional details: The textToPath function generates a CGPath based on the text and its font. The pathToSVG function is supposed to convert the CGPath to an SVG path string. The saved SVG file displays a circular path rather than the intended text.

What I've tried:

I've checked the transformation applied to the path, and it seems correct for inverting the Y-axis. I tried using both M and L for path commands, but the SVG still does not render the text as expected. Any help would be greatly appreciated. Thanks in advance!

Share Improve this question edited Jan 29 at 15:45 herrstrietzel 18.1k2 gold badges27 silver badges53 bronze badges asked Jan 29 at 5:15 HeWhoRemainsHeWhoRemains 6511 bronze badges 2
  • The addQuadCurveToPoint and addCurveToPoint, the values are the same pointee coordinates? I would expect different ones. On your screenshot, the one in the blue case is the incorrect one? – Larme Commented Jan 29 at 15:20
  • Not familiar with the cgpath syntax but currently you're assigning the same points to all command control point coordinates - resulting in a polygon output. element.points quite likely contains an array of points: E.g 2 points for quadratic, 3 points for cubic beziers. Depending on the point properties structure it should rather be something like this svgString += "Q \(points[0].pointee.x) \(points[0].pointee.y) \(points[1].pointee.x) \(points[1].pointee.y) " So assign 1st control point coordinates to SVG control point 1 x/y coordinates, 2nd to 2nd etc. – herrstrietzel Commented Jan 29 at 15:45
Add a comment  | 

1 Answer 1

Reset to default 0

Main problem is not while generating path it's perfect problem is while saving it to SVG that time it's remove curves so this is the updated function to convert Path To SVG which keep curves

func pathToSVG(path: CGPath, color: UIColor) -> String {
        let boundingBox = path.boundingBox
        let svgWidth = boundingBox.width
        let svgHeight = boundingBox.height
        
        let padding: CGFloat = 30.0
        let adjustedWidth = svgWidth + padding
        let adjustedHeight = svgHeight + padding * 2
        
        var svgString = "<svg width=\"\(adjustedWidth)\" height=\"\(adjustedHeight)\" xmlns=\"http://www.w3./2000/svg\" viewBox=\"0 0 \(adjustedWidth) \(adjustedHeight)\">\n"
        svgString += "<path d=\""
        
        var transform = CGAffineTransform(translationX: padding / 2, y: padding)
        let transformPointerUnsafe = withUnsafePointer(to: &transform) { $0 }
        let transformedPath = path.copy(using: transformPointerUnsafe)
        
        transformedPath?.applyWithBlock { elementPointer in
            let element = elementPointer.pointee
            let points = element.points
            
            switch element.type {
            case .moveToPoint:
                svgString += "M \(points[0].x) \(points[0].y) "
            case .addLineToPoint:
                svgString += "L \(points[0].x) \(points[0].y) "
            case .addQuadCurveToPoint:
                svgString += "Q \(points[0].x) \(points[0].y) \(points[1].x) \(points[1].y) "
            case .addCurveToPoint:
                svgString += "C \(points[0].x) \(points[0].y) \(points[1].x) \(points[1].y) \(points[2].x) \(points[2].y) "
            case .closeSubpath:
                svgString += "Z "
            @unknown default:
                break
            }
        }
        
        svgString += "\" fill=\"\(color.hexString)\" />\n"
        svgString += "</svg>"
        
        return svgString
    }

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论