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.org/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!
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.org/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
}