Files
konverter/scripts/gen-icon.swift
T
TZGyn 2d031e58a4 Add Konverter: a local-only menu bar file converter
Drag files in Finder, hold a modifier, and drop on a wheel of outputs.
Conversions run entirely on-device through ImageIO, PDFKit, AVFoundation,
the bundled CLI tools, and optional ffmpeg/LibreOffice.

Includes a --self-test suite (41 checks) and a --convert CLI mode so the
conversion matrix can be verified without the UI.
2026-10-20 00:36:40 +08:00

114 lines
4.3 KiB
Swift

import AppKit
// Draws the Konverter icon: a warm tangerine squircle with a radial conversion
// wheel on it. Everything is vector-drawn so the script has no dependencies.
let canvas: CGFloat = 1024
let size = NSSize(width: canvas, height: canvas)
guard let rep = NSBitmapImageRep(
bitmapDataPlanes: nil, pixelsWide: Int(canvas), pixelsHigh: Int(canvas),
bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false,
colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0
) else { exit(1) }
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: rep)
NSGraphicsContext.current?.imageInterpolation = .high
let rect = NSRect(origin: .zero, size: size)
// macOS icons live inside a ~10% margin; the squircle fills the rest.
let body = rect.insetBy(dx: 96, dy: 96)
let squircle = NSBezierPath(roundedRect: body, xRadius: 200, yRadius: 200)
let gradient = NSGradient(colors: [
NSColor(calibratedRed: 1.00, green: 0.66, blue: 0.20, alpha: 1),
NSColor(calibratedRed: 0.94, green: 0.33, blue: 0.11, alpha: 1),
])!
gradient.draw(in: squircle, angle: -60)
// Soft top highlight.
NSGraphicsContext.saveGraphicsState()
squircle.addClip()
NSGradient(colors: [
NSColor.white.withAlphaComponent(0.28),
NSColor.white.withAlphaComponent(0.00),
])!.draw(in: NSRect(x: body.minX, y: body.midY, width: body.width, height: body.height / 2), angle: -90)
NSGraphicsContext.restoreGraphicsState()
// Radial wheel: eight segments, one of them highlighted as the selection.
let center = NSPoint(x: rect.midX, y: rect.midY + 24)
let outer: CGFloat = 296
let inner: CGFloat = 150
let segmentCount = 8
let gap: CGFloat = 3.2
func polar(_ radius: CGFloat, _ degrees: CGFloat) -> NSPoint {
let radians = degrees * .pi / 180
return NSPoint(x: center.x + radius * cos(radians), y: center.y + radius * sin(radians))
}
func segment(_ index: Int, from start: CGFloat, to end: CGFloat) -> NSBezierPath {
let path = NSBezierPath()
path.move(to: polar(inner, start))
path.line(to: polar(outer, start))
path.appendArc(withCenter: center, radius: outer, startAngle: start, endAngle: end)
path.line(to: polar(inner, end))
path.appendArc(withCenter: center, radius: inner, startAngle: end, endAngle: start, clockwise: true)
path.close()
return path
}
for index in 0..<segmentCount {
let centre = 90 - CGFloat(index) * (360 / CGFloat(segmentCount))
let start = centre - (360 / CGFloat(segmentCount)) / 2 + gap
let end = centre + (360 / CGFloat(segmentCount)) / 2 - gap
let path = segment(index, from: start, to: end)
// Index 4 sits at -90°, i.e. straight down — where the arrow points.
let selected = index == 4
(selected ? NSColor.white : NSColor.white.withAlphaComponent(0.34)).setFill()
path.fill()
}
// Hub with a downward arrow: the drop that performs the conversion.
let hubRadius: CGFloat = 118
let hubRect = NSRect(
x: center.x - hubRadius, y: center.y - hubRadius,
width: hubRadius * 2, height: hubRadius * 2
)
NSColor(calibratedRed: 0.16, green: 0.10, blue: 0.10, alpha: 0.92).setFill()
NSBezierPath(ovalIn: hubRect).fill()
NSColor.white.setFill()
NSBezierPath(ovalIn: hubRect.insetBy(dx: 14, dy: 14)).fill()
// Arrow glyph, pointing at the selected segment.
let arrow = NSBezierPath()
let up = center.y + 58
let down = center.y - 58
let shaftWidth: CGFloat = 26
arrow.move(to: NSPoint(x: center.x - shaftWidth / 2, y: up))
arrow.line(to: NSPoint(x: center.x + shaftWidth / 2, y: up))
arrow.line(to: NSPoint(x: center.x + shaftWidth / 2, y: down + 30))
arrow.line(to: NSPoint(x: center.x + 62, y: down + 30))
arrow.line(to: NSPoint(x: center.x, y: down - 32))
arrow.line(to: NSPoint(x: center.x - 62, y: down + 30))
arrow.line(to: NSPoint(x: center.x - shaftWidth / 2, y: down + 30))
arrow.close()
arrow.lineJoinStyle = .round
arrow.lineWidth = 6
NSColor(calibratedRed: 0.86, green: 0.28, blue: 0.09, alpha: 1).setFill()
NSColor(calibratedRed: 0.86, green: 0.28, blue: 0.09, alpha: 1).setStroke()
arrow.fill()
arrow.stroke()
NSGraphicsContext.restoreGraphicsState()
guard CommandLine.arguments.count >= 2 else { exit(1) }
guard let data = rep.representation(using: .png, properties: [:]) else { exit(1) }
do {
try data.write(to: URL(fileURLWithPath: CommandLine.arguments[1]))
} catch {
exit(1)
}