// Auteur : Simon-Pierre Boucher — contact@spboucher.ai // Génère l'icône iOS 1024×1024 à partir du wordmark Vrai-Prix // (papier #f5f3ee, encre #141814, accent #ff5148, Space Grotesk Bold), // fidèle au logo du site : « Vrai » en encre + « Prix » en boîte encre // pivotée de -2° avec texte accent et ombre décalée dure. // Usage : swift make-icon.swift import AppKit import CoreText let args = CommandLine.arguments guard args.count == 3 else { FileHandle.standardError.write("usage: swift make-icon.swift \n".data(using: .utf8)!) exit(1) } let fontsDir = args[1] let outPath = args[2] // Enregistrer Space Grotesk Bold let fontURL = URL(fileURLWithPath: "\(fontsDir)/SpaceGrotesk-Bold.ttf") CTFontManagerRegisterFontsForURL(fontURL as CFURL, .process, nil) func color(_ hex: UInt32) -> NSColor { NSColor(srgbRed: CGFloat((hex >> 16) & 0xff) / 255, green: CGFloat((hex >> 8) & 0xff) / 255, blue: CGFloat(hex & 0xff) / 255, alpha: 1) } let paper = color(0xF5F3EE) let ink = color(0x141814) let lime = color(0xFF5148) let S: CGFloat = 1024 // contexte bitmap exact 1024×1024, sans alpha (exigence App Store) guard let ctx = CGContext( data: nil, width: Int(S), height: Int(S), bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpace(name: CGColorSpace.sRGB)!, bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue ) else { exit(1) } NSGraphicsContext.current = NSGraphicsContext(cgContext: ctx, flipped: false) // fond papier plein cadre (iOS applique lui-même le masque arrondi) paper.setFill() ctx.fill(CGRect(x: 0, y: 0, width: S, height: S)) // grain très léger façon papier (points encre à faible alpha, déterministe) ctx.setFillColor(ink.withAlphaComponent(0.028).cgColor) var seed: UInt64 = 0x5EED_CAFE func rnd() -> CGFloat { seed = seed &* 6364136223846793005 &+ 1442695040888963407 return CGFloat((seed >> 33) % 10_000) / 10_000 } for _ in 0..<2600 { let x = rnd() * S, y = rnd() * S, r = 1.0 + rnd() * 1.6 ctx.fillEllipse(in: CGRect(x: x, y: y, width: r, height: r)) } let font = NSFont(name: "SpaceGrotesk-Bold", size: 252)! func attrString(_ s: String, _ c: NSColor) -> NSAttributedString { NSAttributedString(string: s, attributes: [.font: font, .foregroundColor: c, .kern: -10]) } // mesures let vrai = attrString("Vrai", ink) let prix = attrString("Prix", lime) let vraiSize = vrai.size() let prixSize = prix.size() // boîte « Prix » : encre, coins arrondis, pivotée -2°, ombre décalée dure let padX: CGFloat = 46 let padY: CGFloat = 6 let boxW = prixSize.width + padX * 2 let boxH = prixSize.height + padY * 2 let gap: CGFloat = 34 let totalH = vraiSize.height + gap + boxH // repère depuis le haut (coordonnées AppKit = origine en bas), remonté pour dégager la micro-étiquette let topY = (S - totalH) / 2 + totalH + 56 // « Vrai » centré en haut du bloc let vraiOrigin = CGPoint(x: (S - vraiSize.width) / 2, y: topY - vraiSize.height) vrai.draw(at: vraiOrigin) // boîte pivotée autour de son centre let boxCenter = CGPoint(x: S / 2, y: topY - vraiSize.height - gap - boxH / 2) ctx.saveGState() ctx.translateBy(x: boxCenter.x, y: boxCenter.y) ctx.rotate(by: -2 * .pi / 180) // ombre décalée dure (signature du design) let boxRect = CGRect(x: -boxW / 2, y: -boxH / 2, width: boxW, height: boxH) let shadowPath = CGPath(roundedRect: boxRect.offsetBy(dx: 14, dy: -14), cornerWidth: 28, cornerHeight: 28, transform: nil) ctx.addPath(shadowPath) ctx.setFillColor(ink.withAlphaComponent(0.22).cgColor) ctx.fillPath() let boxPath = CGPath(roundedRect: boxRect, cornerWidth: 28, cornerHeight: 28, transform: nil) ctx.addPath(boxPath) ctx.setFillColor(ink.cgColor) ctx.fillPath() prix.draw(at: CGPoint(x: -prixSize.width / 2, y: -prixSize.height / 2 + 6)) ctx.restoreGState() // micro-étiquette mono en bas : trait + « QUÉBEC » let microFont = NSFont(name: "SpaceGrotesk-Bold", size: 44)! let micro = NSAttributedString(string: "QUÉBEC", attributes: [ .font: microFont, .foregroundColor: color(0x9E2A25), .kern: 14, ]) let mSize = micro.size() micro.draw(at: CGPoint(x: (S - mSize.width) / 2, y: 118)) let lineW: CGFloat = 56 ctx.setFillColor(color(0x9E2A25).cgColor) ctx.fill(CGRect(x: (S - lineW) / 2, y: 96, width: lineW, height: 6)) guard let cgImage = ctx.makeImage() else { exit(1) } let rep = NSBitmapImageRep(cgImage: cgImage) guard let png = rep.representation(using: .png, properties: [:]) else { exit(1) } try! png.write(to: URL(fileURLWithPath: outPath)) print("OK \(outPath)")