Skip to content

Instantly share code, notes, and snippets.

@Koshimizu-Takehito
Created April 20, 2025 10:06
Show Gist options
  • Save Koshimizu-Takehito/145e89f7f76bca2f46a81ed9b616d5b2 to your computer and use it in GitHub Desktop.
Save Koshimizu-Takehito/145e89f7f76bca2f46a81ed9b616d5b2 to your computer and use it in GitHub Desktop.
TextShape
import SwiftUI
struct ContentView: View {
var body: some View {
TextShape(sample)
.fill(.orange)
.stroke(.blue, lineWidth: 2)
}
var sample: AttributedString {
var text = AttributedString("Hello, world!")
text.font = UIFont(name: "HiraginoSans-W8", size: 50)
text.foregroundColor = .label
return text
}
}
struct TextShape: Shape {
var text: AttributedString
init(_ text: AttributedString) {
self.text = text
}
func path(in rect: CGRect) -> Path {
let font: UIFont = text.font ?? .systemFont(ofSize: UIFont.systemFontSize)
let ctFont = CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil)
let attr = [NSAttributedString.Key.font: ctFont]
let line = CTLineCreateWithAttributedString(
NSAttributedString(string: String(text.characters), attributes: attr)
)
let runs = CTLineGetGlyphRuns(line) as! [CTRun]
let rawPath = CGMutablePath()
for run in runs {
let count = CTRunGetGlyphCount(run)
var glyphs = [CGGlyph](repeating: 0, count: count)
var positions = [CGPoint](repeating: .zero, count: count)
CTRunGetGlyphs(run, CFRange(location: 0, length: 0), &glyphs)
CTRunGetPositions(run, CFRange(location: 0, length: 0), &positions)
for i in 0..<count {
if let path = CTFontCreatePathForGlyph(ctFont, glyphs[i], nil) {
let t = CGAffineTransform(translationX: positions[i].x, y: positions[i].y)
rawPath.addPath(path, transform: t)
}
}
}
// CGPath を上下反転( iOS の座標系を考慮 )
var flip = CGAffineTransform(scaleX: 1, y: -1)
let flipped = rawPath.copy(using: &flip)!
let b = flipped.boundingBox
let dx = (rect.width - b.width) / 2 - b.minX
let dy = (rect.height - b.height) / 2 - b.minY
var center = CGAffineTransform(translationX: dx, y: dy)
return flipped.copy(using: &center).map(Path.init) ?? Path()
}
}
#Preview {
ContentView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment