import AppKit import CoreGraphics import Foundation enum IconStyle: String { case flat = "Flat iOS-native" case glossy = "Glossy iOS 26" } enum Concept: CaseIterable { case routeLens case splitLock case packetPrism case tunnelPulse case stackedRequests var title: String { switch self { case .routeLens: return "Route Lens" case .splitLock: return "Split Lock" case .packetPrism: return "Packet Prism" case .tunnelPulse: return "Tunnel Pulse" case .stackedRequests: return "Stacked Requests" } } var summary: String { switch self { case .routeLens: return "Inspect a live network path through a focused lens." case .splitLock: return "Show secure traffic opening into readable traces." case .packetPrism: return "Decode one incoming packet into multiple visible layers." case .tunnelPulse: return "Represent proxy transit as a bright pulse through a tunnel." case .stackedRequests: return "Frame the app as a polished request browser." } } var notes: [String] { switch self { case .routeLens: return [ "Best default option for broad appeal.", "Reads clearly at small sizes.", "Feels more like inspection than blocking." ] case .splitLock: return [ "Most explicit HTTPS inspection metaphor.", "Feels technical without looking hostile.", "Strong choice if SSL proxying is central to the brand." ] case .packetPrism: return [ "Highest-end and most design-forward direction.", "Communicates transform and decode cleanly.", "Best fit for a premium developer tool." ] case .tunnelPulse: return [ "Least literal and most atmospheric option.", "Feels modern, fast, and infrastructural.", "Works well if you want less obvious symbolism." ] case .stackedRequests: return [ "Most product-led and browser-like.", "Hints at request rows and response status.", "Useful if the app should feel approachable." ] } } } struct Page { static let size = CGSize(width: 900, height: 1200) } let outputDirectory = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) .appendingPathComponent("Design/icon_concepts", isDirectory: true) let pdfURL = outputDirectory.appendingPathComponent("proxy_icon_concepts.pdf") let pngURL = outputDirectory.appendingPathComponent("proxy_icon_concepts_preview.png") try FileManager.default.createDirectory(at: outputDirectory, withIntermediateDirectories: true) renderPDF(to: pdfURL) renderPreviewPNG(to: pngURL) print("Generated:") print(pdfURL.path) print(pngURL.path) func renderPDF(to url: URL) { var mediaBox = CGRect(origin: .zero, size: Page.size) guard let context = CGContext(url as CFURL, mediaBox: &mediaBox, nil) else { fatalError("Unable to create PDF context") } beginPDFPage(context, mediaBox) { drawCoverPage(in: mediaBox) } for concept in Concept.allCases { beginPDFPage(context, mediaBox) { drawDetailPage(for: concept, in: mediaBox) } } context.closePDF() } func renderPreviewPNG(to url: URL) { let previewSize = CGSize(width: 1500, height: 2000) let image = NSImage(size: previewSize) image.lockFocus() NSGraphicsContext.current?.imageInterpolation = .high let context = NSGraphicsContext.current?.cgContext let scale = previewSize.width / Page.size.width context?.saveGState() context?.scaleBy(x: scale, y: scale) drawCoverPage(in: CGRect(origin: .zero, size: Page.size)) context?.restoreGState() image.unlockFocus() guard let tiff = image.tiffRepresentation, let rep = NSBitmapImageRep(data: tiff), let png = rep.representation(using: .png, properties: [:]) else { fatalError("Unable to create PNG preview") } try? png.write(to: url) } func beginPDFPage(_ context: CGContext, _ mediaBox: CGRect, draw: () -> Void) { context.beginPDFPage(nil) let graphicsContext = NSGraphicsContext(cgContext: context, flipped: false) NSGraphicsContext.saveGraphicsState() NSGraphicsContext.current = graphicsContext draw() NSGraphicsContext.restoreGraphicsState() context.endPDFPage() } func drawCoverPage(in rect: CGRect) { fill(rect, color: NSColor(hex: 0xF4F6FB)) drawText( "Proxy App Icon Concepts", in: CGRect(x: 70, y: rect.height - 120, width: rect.width - 140, height: 44), font: .systemFont(ofSize: 32, weight: .bold), color: NSColor(hex: 0x101828) ) drawText( "Five directions, each tuned for an iOS-native flat pass and a layered glossy pass.", in: CGRect(x: 70, y: rect.height - 165, width: rect.width - 140, height: 28), font: .systemFont(ofSize: 16, weight: .medium), color: NSColor(hex: 0x475467) ) let cellWidth = (rect.width - 70 * 2 - 28) / 2 let cellHeight: CGFloat = 300 let rows: [(Concept, CGRect)] = [ (.routeLens, CGRect(x: 70, y: 760, width: cellWidth, height: cellHeight)), (.splitLock, CGRect(x: 70 + cellWidth + 28, y: 760, width: cellWidth, height: cellHeight)), (.packetPrism, CGRect(x: 70, y: 430, width: cellWidth, height: cellHeight)), (.tunnelPulse, CGRect(x: 70 + cellWidth + 28, y: 430, width: cellWidth, height: cellHeight)), (.stackedRequests, CGRect(x: 70, y: 100, width: cellWidth, height: cellHeight)) ] for (concept, frame) in rows { drawCoverCard(for: concept, in: frame) } drawSelectionCard( in: CGRect(x: 70 + cellWidth + 28, y: 100, width: cellWidth, height: cellHeight), accent: NSColor(hex: 0x4F86FF) ) } func drawDetailPage(for concept: Concept, in rect: CGRect) { fill(rect, color: NSColor(hex: 0xF7F8FC)) drawText( concept.title, in: CGRect(x: 72, y: rect.height - 120, width: rect.width - 144, height: 40), font: .systemFont(ofSize: 34, weight: .bold), color: NSColor(hex: 0x101828) ) drawText( concept.summary, in: CGRect(x: 72, y: rect.height - 162, width: rect.width - 144, height: 26), font: .systemFont(ofSize: 17, weight: .medium), color: NSColor(hex: 0x475467) ) let cardWidth = (rect.width - 72 * 2 - 30) / 2 let cardHeight: CGFloat = 520 let y: CGFloat = 430 drawVariantCard( title: IconStyle.flat.rawValue, subtitle: "Crisp, simplified, and system-friendly.", concept: concept, style: .flat, frame: CGRect(x: 72, y: y, width: cardWidth, height: cardHeight) ) drawVariantCard( title: IconStyle.glossy.rawValue, subtitle: "Layered, luminous, and ready for Liquid Glass.", concept: concept, style: .glossy, frame: CGRect(x: 72 + cardWidth + 30, y: y, width: cardWidth, height: cardHeight) ) drawNotesCard( notes: concept.notes, frame: CGRect(x: 72, y: 88, width: rect.width - 144, height: 250) ) } func drawCoverCard(for concept: Concept, in frame: CGRect) { drawSurface(frame, fill: NSColor.white, stroke: NSColor(hex: 0xD8DEEA)) let iconRect = CGRect(x: frame.minX + 24, y: frame.minY + 72, width: 156, height: 156) drawIcon(concept: concept, style: .glossy, in: iconRect) drawText( concept.title, in: CGRect(x: frame.minX + 204, y: frame.maxY - 70, width: frame.width - 224, height: 30), font: .systemFont(ofSize: 22, weight: .semibold), color: NSColor(hex: 0x111827) ) drawText( concept.summary, in: CGRect(x: frame.minX + 204, y: frame.maxY - 132, width: frame.width - 228, height: 64), font: .systemFont(ofSize: 14, weight: .medium), color: NSColor(hex: 0x475467) ) drawPill("Glossy preview", in: CGRect(x: frame.minX + 204, y: frame.minY + 28, width: 110, height: 28), fill: NSColor(hex: 0xEAF1FF), textColor: NSColor(hex: 0x2457D6)) } func drawSelectionCard(in frame: CGRect, accent: NSColor) { drawSurface(frame, fill: NSColor(hex: 0x101828), stroke: accent.withAlphaComponent(0.3)) drawText( "Pick One", in: CGRect(x: frame.minX + 26, y: frame.maxY - 76, width: frame.width - 52, height: 28), font: .systemFont(ofSize: 24, weight: .bold), color: .white ) drawText( "Choose the direction with the strongest metaphor. I can then iterate on color, shape weight, gloss, and app-store readability.", in: CGRect(x: frame.minX + 26, y: frame.maxY - 154, width: frame.width - 52, height: 84), font: .systemFont(ofSize: 15, weight: .medium), color: NSColor.white.withAlphaComponent(0.84) ) let checklist = [ "Best all-around: Route Lens", "Best technical: Split Lock", "Best premium: Packet Prism" ] for (index, item) in checklist.enumerated() { let rowY = frame.minY + 112 - CGFloat(index) * 34 let dotRect = CGRect(x: frame.minX + 28, y: rowY + 6, width: 10, height: 10) fillEllipse(dotRect, color: accent) drawText( item, in: CGRect(x: frame.minX + 48, y: rowY, width: frame.width - 72, height: 20), font: .systemFont(ofSize: 15, weight: .semibold), color: .white ) } drawPill("PDF pages include flat + glossy", in: CGRect(x: frame.minX + 26, y: frame.minY + 24, width: 200, height: 30), fill: accent.withAlphaComponent(0.18), textColor: .white) } func drawVariantCard(title: String, subtitle: String, concept: Concept, style: IconStyle, frame: CGRect) { drawSurface(frame, fill: NSColor.white, stroke: NSColor(hex: 0xD8DEEA)) drawText( title, in: CGRect(x: frame.minX + 28, y: frame.maxY - 54, width: frame.width - 56, height: 26), font: .systemFont(ofSize: 22, weight: .bold), color: NSColor(hex: 0x111827) ) drawText( subtitle, in: CGRect(x: frame.minX + 28, y: frame.maxY - 82, width: frame.width - 56, height: 22), font: .systemFont(ofSize: 14, weight: .medium), color: NSColor(hex: 0x667085) ) let iconRect = CGRect( x: frame.midX - 132, y: frame.midY - 40, width: 264, height: 264 ) drawIcon(concept: concept, style: style, in: iconRect) let note = style == .flat ? "Keep the idea direct, centered, and clean." : "Preserve the same concept but express it with layered depth." drawText( note, in: CGRect(x: frame.minX + 28, y: frame.minY + 40, width: frame.width - 56, height: 40), font: .systemFont(ofSize: 15, weight: .medium), color: NSColor(hex: 0x475467) ) } func drawNotesCard(notes: [String], frame: CGRect) { drawSurface(frame, fill: NSColor.white, stroke: NSColor(hex: 0xD8DEEA)) drawText( "What To Evaluate", in: CGRect(x: frame.minX + 26, y: frame.maxY - 46, width: frame.width - 52, height: 24), font: .systemFont(ofSize: 22, weight: .bold), color: NSColor(hex: 0x111827) ) for (index, note) in notes.enumerated() { let rowY = frame.maxY - 92 - CGFloat(index) * 46 fillEllipse(CGRect(x: frame.minX + 30, y: rowY + 8, width: 10, height: 10), color: NSColor(hex: 0x4F86FF)) drawText( note, in: CGRect(x: frame.minX + 52, y: rowY, width: frame.width - 82, height: 30), font: .systemFont(ofSize: 16, weight: .medium), color: NSColor(hex: 0x475467) ) } } func drawIcon(concept: Concept, style: IconStyle, in rect: CGRect) { drawIconBackground(style: style, in: rect) switch concept { case .routeLens: drawRouteLens(style: style, in: rect.insetBy(dx: 18, dy: 18)) case .splitLock: drawSplitLock(style: style, in: rect.insetBy(dx: 18, dy: 18)) case .packetPrism: drawPacketPrism(style: style, in: rect.insetBy(dx: 18, dy: 18)) case .tunnelPulse: drawTunnelPulse(style: style, in: rect.insetBy(dx: 18, dy: 18)) case .stackedRequests: drawStackedRequests(style: style, in: rect.insetBy(dx: 18, dy: 18)) } } func drawIconBackground(style: IconStyle, in rect: CGRect) { let path = NSBezierPath(roundedRect: rect, xRadius: rect.width * 0.23, yRadius: rect.height * 0.23) switch style { case .flat: let gradient = NSGradient(colors: [ NSColor(hex: 0xFAFBFF), NSColor(hex: 0xEEF3FF) ]) gradient?.draw(in: path, angle: 90) NSColor.white.withAlphaComponent(0.55).setStroke() path.lineWidth = 1.5 path.stroke() case .glossy: let gradient = NSGradient(colors: [ NSColor(hex: 0xF4F8FF), NSColor(hex: 0xDCE8FF), NSColor(hex: 0xC6D9FF) ]) gradient?.draw(in: path, angle: 90) let topHighlight = NSBezierPath(roundedRect: rect.insetBy(dx: 18, dy: 18).offsetBy(dx: 0, dy: 24), xRadius: 40, yRadius: 40) NSColor.white.withAlphaComponent(0.28).setFill() topHighlight.fill() NSColor.white.withAlphaComponent(0.7).setStroke() path.lineWidth = 1.7 path.stroke() } } func drawRouteLens(style: IconStyle, in rect: CGRect) { let line = NSBezierPath() line.move(to: CGPoint(x: rect.minX + 12, y: rect.midY - 24)) line.curve( to: CGPoint(x: rect.maxX - 8, y: rect.midY + 12), controlPoint1: CGPoint(x: rect.minX + 56, y: rect.maxY - 8), controlPoint2: CGPoint(x: rect.maxX - 92, y: rect.minY + 30) ) line.lineWidth = style == .flat ? 12 : 14 line.lineCapStyle = .round NSColor(hex: style == .flat ? 0xA8C4FF : 0x85B5FF).setStroke() line.stroke() let lensRect = CGRect(x: rect.midX - 44, y: rect.midY - 12, width: 88, height: 88) let lens = NSBezierPath(ovalIn: lensRect) if style == .flat { NSColor.white.setFill() lens.fill() NSColor(hex: 0x2764EA).setStroke() } else { let gradient = NSGradient(colors: [ NSColor.white.withAlphaComponent(0.92), NSColor(hex: 0xEAF2FF).withAlphaComponent(0.92) ]) gradient?.draw(in: lens, angle: 90) NSColor(hex: 0x3F7CFF).setStroke() } lens.lineWidth = style == .flat ? 12 : 10 lens.stroke() let handle = NSBezierPath() handle.move(to: CGPoint(x: lensRect.maxX - 4, y: lensRect.minY + 8)) handle.line(to: CGPoint(x: rect.maxX - 24, y: rect.minY + 22)) handle.lineWidth = style == .flat ? 14 : 16 handle.lineCapStyle = .round NSColor(hex: 0x235BDA).setStroke() handle.stroke() fillEllipse(CGRect(x: rect.midX - 10, y: rect.midY + 14, width: 20, height: 20), color: NSColor(hex: 0x3AD4FF)) } func drawSplitLock(style: IconStyle, in rect: CGRect) { let bodyRect = CGRect(x: rect.midX - 66, y: rect.midY - 34, width: 132, height: 104) let body = NSBezierPath(roundedRect: bodyRect, xRadius: 28, yRadius: 28) if style == .flat { NSColor(hex: 0xF7FBFF).setFill() body.fill() NSColor(hex: 0x0F172A).setStroke() body.lineWidth = 7 body.stroke() } else { let gradient = NSGradient(colors: [ NSColor.white.withAlphaComponent(0.92), NSColor(hex: 0xDCE8FF).withAlphaComponent(0.9) ]) gradient?.draw(in: body, angle: 90) NSColor.white.withAlphaComponent(0.88).setStroke() body.lineWidth = 6 body.stroke() } let shackle = NSBezierPath() shackle.move(to: CGPoint(x: rect.midX - 42, y: bodyRect.maxY - 2)) shackle.curve( to: CGPoint(x: rect.midX + 14, y: bodyRect.maxY + 52), controlPoint1: CGPoint(x: rect.midX - 44, y: bodyRect.maxY + 54), controlPoint2: CGPoint(x: rect.midX + 14, y: bodyRect.maxY + 54) ) shackle.lineWidth = 16 shackle.lineCapStyle = .round NSColor(hex: style == .flat ? 0x0F172A : 0xF8FAFC).setStroke() shackle.stroke() let splitMask = NSBezierPath(rect: CGRect(x: rect.midX - 2, y: bodyRect.minY - 10, width: bodyRect.width / 2 + 18, height: bodyRect.height + 96)) NSGraphicsContext.saveGraphicsState() splitMask.addClip() let leftFill = NSBezierPath(roundedRect: bodyRect, xRadius: 28, yRadius: 28) NSColor(hex: style == .flat ? 0x2B6CF3 : 0xA6CAFF).setFill() leftFill.fill() NSGraphicsContext.restoreGraphicsState() let traces = [ (CGPoint(x: rect.midX + 8, y: rect.midY + 20), CGPoint(x: rect.maxX - 18, y: rect.midY + 44)), (CGPoint(x: rect.midX + 14, y: rect.midY), CGPoint(x: rect.maxX - 10, y: rect.midY + 4)), (CGPoint(x: rect.midX + 10, y: rect.midY - 22), CGPoint(x: rect.maxX - 24, y: rect.midY - 38)) ] for (start, end) in traces { let trace = NSBezierPath() trace.move(to: start) trace.curve( to: end, controlPoint1: CGPoint(x: start.x + 28, y: start.y), controlPoint2: CGPoint(x: end.x - 22, y: end.y) ) trace.lineWidth = 7 trace.lineCapStyle = .round NSColor(hex: 0x56A7FF).setStroke() trace.stroke() fillEllipse(CGRect(x: end.x - 6, y: end.y - 6, width: 12, height: 12), color: NSColor(hex: 0x56A7FF)) } } func drawPacketPrism(style: IconStyle, in rect: CGRect) { let prism = NSBezierPath() prism.move(to: CGPoint(x: rect.midX - 18, y: rect.midY + 52)) prism.line(to: CGPoint(x: rect.midX + 52, y: rect.midY)) prism.line(to: CGPoint(x: rect.midX - 18, y: rect.midY - 52)) prism.close() if style == .flat { NSColor(hex: 0xEFF5FF).setFill() prism.fill() NSColor(hex: 0x2563EB).setStroke() prism.lineWidth = 7 prism.stroke() } else { let gradient = NSGradient(colors: [ NSColor.white.withAlphaComponent(0.92), NSColor(hex: 0xD7E6FF).withAlphaComponent(0.88) ]) gradient?.draw(in: prism, angle: 90) NSColor.white.withAlphaComponent(0.9).setStroke() prism.lineWidth = 6 prism.stroke() } let incoming = NSBezierPath() incoming.move(to: CGPoint(x: rect.minX + 18, y: rect.midY)) incoming.line(to: CGPoint(x: rect.midX - 18, y: rect.midY)) incoming.lineWidth = 10 incoming.lineCapStyle = .round NSColor(hex: 0x2D6BFF).setStroke() incoming.stroke() fillEllipse(CGRect(x: rect.minX + 8, y: rect.midY - 10, width: 20, height: 20), color: NSColor(hex: 0x2D6BFF)) let strands: [(CGFloat, Int)] = [(34, 0x2D6BFF), (0, 0x2ED1FF), (-34, 0x46C57A)] for (offset, colorHex) in strands { let strand = NSBezierPath() strand.move(to: CGPoint(x: rect.midX + 42, y: rect.midY + offset * 0.25)) strand.curve( to: CGPoint(x: rect.maxX - 18, y: rect.midY + offset), controlPoint1: CGPoint(x: rect.midX + 72, y: rect.midY + offset * 0.28), controlPoint2: CGPoint(x: rect.maxX - 44, y: rect.midY + offset) ) strand.lineWidth = 9 strand.lineCapStyle = .round NSColor(hex: colorHex).setStroke() strand.stroke() } } func drawTunnelPulse(style: IconStyle, in rect: CGRect) { let outerArc = NSBezierPath() outerArc.appendArc( withCenter: CGPoint(x: rect.midX, y: rect.midY - 4), radius: rect.width * 0.34, startAngle: 208, endAngle: -28, clockwise: false ) outerArc.lineWidth = style == .flat ? 26 : 30 outerArc.lineCapStyle = .round NSColor(hex: style == .flat ? 0x0F305F : 0x1C3F7C).setStroke() outerArc.stroke() let innerArc = NSBezierPath() innerArc.appendArc( withCenter: CGPoint(x: rect.midX, y: rect.midY - 4), radius: rect.width * 0.22, startAngle: 212, endAngle: -32, clockwise: false ) innerArc.lineWidth = style == .flat ? 16 : 18 innerArc.lineCapStyle = .round NSColor(hex: style == .flat ? 0x55A9FF : 0x7DCAFF).setStroke() innerArc.stroke() let pulseRect = CGRect(x: rect.midX - 24, y: rect.midY - 24, width: 48, height: 48) if style == .glossy { let halo = NSBezierPath(ovalIn: pulseRect.insetBy(dx: -18, dy: -18)) NSColor(hex: 0x6DEBFF).withAlphaComponent(0.18).setFill() halo.fill() } fillEllipse(pulseRect, color: NSColor(hex: style == .flat ? 0x19D7FF : 0x56F2FF)) fillEllipse(pulseRect.insetBy(dx: 12, dy: 12), color: NSColor.white.withAlphaComponent(0.92)) } func drawStackedRequests(style: IconStyle, in rect: CGRect) { let back = CGRect(x: rect.midX - 76, y: rect.midY + 18, width: 132, height: 90) let middle = CGRect(x: rect.midX - 94, y: rect.midY - 10, width: 148, height: 100) let front = CGRect(x: rect.midX - 112, y: rect.midY - 46, width: 164, height: 116) drawMiniCard(back, fill: style == .flat ? NSColor(hex: 0xE6EEFf) : NSColor.white.withAlphaComponent(0.48)) drawMiniCard(middle, fill: style == .flat ? NSColor(hex: 0xF5F8FF) : NSColor.white.withAlphaComponent(0.70)) drawMiniCard(front, fill: style == .flat ? NSColor.white : NSColor.white.withAlphaComponent(0.88)) drawPill("GET", in: CGRect(x: front.minX + 18, y: front.maxY - 34, width: 40, height: 22), fill: NSColor(hex: 0xDBF3E4), textColor: NSColor(hex: 0x1E8C4A)) drawPill("200", in: CGRect(x: front.minX + 64, y: front.maxY - 34, width: 44, height: 22), fill: NSColor(hex: 0xE6EEFF), textColor: NSColor(hex: 0x275FD7)) drawText("api/profile", in: CGRect(x: front.minX + 18, y: front.maxY - 64, width: 110, height: 18), font: .systemFont(ofSize: 11, weight: .semibold), color: NSColor(hex: 0x111827)) fill(CGRect(x: front.minX + 18, y: front.maxY - 78, width: 120, height: 4), color: NSColor(hex: 0x4F86FF)) fill(CGRect(x: front.minX + 18, y: front.maxY - 92, width: 96, height: 4), color: NSColor(hex: 0xD0D9E8)) fill(CGRect(x: front.minX + 18, y: front.maxY - 104, width: 78, height: 4), color: NSColor(hex: 0xD0D9E8)) } func drawMiniCard(_ rect: CGRect, fill color: NSColor) { let path = NSBezierPath(roundedRect: rect, xRadius: 22, yRadius: 22) color.setFill() path.fill() NSColor(hex: 0xD6DDEA).withAlphaComponent(0.75).setStroke() path.lineWidth = 1 path.stroke() } func drawSurface(_ rect: CGRect, fill: NSColor, stroke: NSColor) { let shadow = NSShadow() shadow.shadowColor = NSColor.black.withAlphaComponent(0.08) shadow.shadowOffset = CGSize(width: 0, height: -6) shadow.shadowBlurRadius = 18 NSGraphicsContext.saveGraphicsState() shadow.set() let path = NSBezierPath(roundedRect: rect, xRadius: 26, yRadius: 26) fill.setFill() path.fill() NSGraphicsContext.restoreGraphicsState() let strokePath = NSBezierPath(roundedRect: rect, xRadius: 26, yRadius: 26) stroke.setStroke() strokePath.lineWidth = 1.2 strokePath.stroke() } func drawPill(_ title: String, in rect: CGRect, fill: NSColor, textColor: NSColor) { let pill = NSBezierPath(roundedRect: rect, xRadius: rect.height / 2, yRadius: rect.height / 2) fill.setFill() pill.fill() drawText(title, in: rect.offsetBy(dx: 0, dy: 2), font: .systemFont(ofSize: 11, weight: .bold), color: textColor, alignment: .center) } func drawText(_ text: String, in rect: CGRect, font: NSFont, color: NSColor, alignment: NSTextAlignment = .left) { let paragraph = NSMutableParagraphStyle() paragraph.alignment = alignment paragraph.lineBreakMode = .byWordWrapping let attributes: [NSAttributedString.Key: Any] = [ .font: font, .foregroundColor: color, .paragraphStyle: paragraph ] NSString(string: text).draw(with: rect, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attributes) } func fill(_ rect: CGRect, color: NSColor) { color.setFill() NSBezierPath(rect: rect).fill() } func fillEllipse(_ rect: CGRect, color: NSColor) { color.setFill() NSBezierPath(ovalIn: rect).fill() } extension NSColor { convenience init(hex: Int, alpha: CGFloat = 1.0) { let red = CGFloat((hex >> 16) & 0xFF) / 255.0 let green = CGFloat((hex >> 8) & 0xFF) / 255.0 let blue = CGFloat(hex & 0xFF) / 255.0 self.init(calibratedRed: red, green: green, blue: blue, alpha: alpha) } }