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 |1 Answer
Reset to default 0Main 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
}
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745310126a4621955.html
addQuadCurveToPoint
andaddCurveToPoint
, 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:20element.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 thissvgString += "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