Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
4.8 kB · 118 lines
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119// CanvasView — draws the word from the current skeletons + axes, overlays the// editable skeleton, and lets you drag skeleton points to author letterforms.import AppKit
final class Doc { var glyphs = Skeletons.base() var axes = Axes() var word = "regarde" var showSkeleton = true}
final class CanvasView: NSView { var doc = Doc() private var layout: [(ch: Character, penX: CGFloat)] = [] private var scale: CGFloat = 0.4 private var originX: CGFloat = 90 private var originY: CGFloat = 0 private var drag: (ch: Character, si: Int, pi: Int)? = nil
private let ink = NSColor(red: 0.047, green: 0.047, blue: 0.051, alpha: 1) private let paper = NSColor(red: 0.969, green: 0.965, blue: 0.957, alpha: 1) private let red = NSColor(red: 0.898, green: 0.141, blue: 0.165, alpha: 1) private let skel = NSColor(red: 0.0, green: 0.45, blue: 0.95, alpha: 0.9)
override var isFlipped: Bool { false }
private func tanSlant() -> CGFloat { tan(doc.axes.slant * .pi / 180) }
private func toView(_ p: CGPoint, _ penX: CGFloat) -> CGPoint { let gx = p.x * doc.axes.width + penX let gy = p.y * doc.axes.xHeight return CGPoint(x: originX + (gx + tanSlant()*gy) * scale, y: originY + gy * scale) } private func toUnit(_ v: CGPoint, _ penX: CGFloat) -> CGPoint { let gy = (v.y - originY) / scale let gx = (v.x - originX) / scale - tanSlant()*gy return CGPoint(x: (gx - penX) / doc.axes.width, y: gy / doc.axes.xHeight) }
private func relayout() { layout.removeAll() var penX: CGFloat = 0 for ch in doc.word { guard let g = doc.glyphs[ch] else { continue } layout.append((ch, penX)) penX += g.advance * doc.axes.width + doc.axes.tracking } scale = max((bounds.height * 0.46) / Metrics.em, 0.05) originY = bounds.height * 0.36 }
override func draw(_ dirty: NSRect) { guard let ctx = NSGraphicsContext.current?.cgContext else { return } paper.setFill(); ctx.fill(bounds) relayout()
// glyphs ink.setFill() var lastEnd: CGFloat = originX for (ch, penX) in layout { guard let g = doc.glyphs[ch] else { continue } let path = CGMutablePath() for loop in Expander.contours(g, doc.axes) { guard let f = loop.first else { continue } path.move(to: toView(f, penX)) for q in loop.dropFirst() { path.addLine(to: toView(q, penX)) } path.closeSubpath() } ctx.addPath(path); ctx.fillPath(using: .winding) lastEnd = toView(CGPoint(x: g.advance, y: 0), penX).x }
// the ■ mark let sq = Metrics.xHeight * 0.32 * scale * doc.axes.xHeight red.setFill() ctx.fill(CGRect(x: lastEnd + 28, y: originY, width: sq, height: sq))
// skeleton overlay if doc.showSkeleton { for (ch, penX) in layout { guard let g = doc.glyphs[ch] else { continue } for s in g.strokes { ctx.setStrokeColor(skel.withAlphaComponent(0.35).cgColor); ctx.setLineWidth(1) let pts = s.pts.map { toView($0, penX) } if let f = pts.first { ctx.move(to: f); for q in pts.dropFirst() { ctx.addLine(to: q) }; if s.closed, let ff = pts.first { ctx.addLine(to: ff) }; ctx.strokePath() } skel.setFill() for v in pts { ctx.fillEllipse(in: CGRect(x: v.x-3, y: v.y-3, width: 6, height: 6)) } } } } }
// ── editing ── override func mouseDown(with e: NSEvent) { let v = convert(e.locationInWindow, from: nil) var best: (d: CGFloat, ch: Character, si: Int, pi: Int)? = nil for (ch, penX) in layout { guard let g = doc.glyphs[ch] else { continue } for (si, s) in g.strokes.enumerated() { for (pi, p) in s.pts.enumerated() { let vp = toView(p, penX) let d = hypot(vp.x - v.x, vp.y - v.y) if d < 9, best == nil || d < best!.d { best = (d, ch, si, pi) } } } } if let b = best { drag = (b.ch, b.si, b.pi) } } override func mouseDragged(with e: NSEvent) { guard let d = drag else { return } let v = convert(e.locationInWindow, from: nil) let penX = layout.first(where: { $0.ch == d.ch })?.penX ?? 0 let u = toUnit(v, penX) doc.glyphs[d.ch]?.strokes[d.si].pts[d.pi] = u needsDisplay = true } override func mouseUp(with e: NSEvent) { drag = nil }}