Viewing is open to everyone. Downloading and starring need a GitHub sign-in.
Sign in with GitHub
CKeyStyle.swift
v0.1.0-build.85
Latest
Commit ede38f5
Sign in with GitHub to download. Each download is recorded with your GitHub username.
| 1 | import CKeyCodec |
| 2 | import CKeyScanKit |
| 3 | import Foundation |
| 4 | |
| 5 | /// An sRGB colour, 8 bits per channel. |
| 6 | public struct CKeyColor: Hashable, Sendable { |
| 7 | public var red: UInt8 |
| 8 | public var green: UInt8 |
| 9 | public var blue: UInt8 |
| 10 | |
| 11 | public init(red: UInt8, green: UInt8, blue: UInt8) { |
| 12 | self.red = red |
| 13 | self.green = green |
| 14 | self.blue = blue |
| 15 | } |
| 16 | |
| 17 | /// From a 0xRRGGBB value. |
| 18 | public init(hex: UInt32) { |
| 19 | self.init(red: UInt8((hex >> 16) & 0xff), green: UInt8((hex >> 8) & 0xff), blue: UInt8(hex & 0xff)) |
| 20 | } |
| 21 | |
| 22 | public static let black = CKeyColor(red: 0, green: 0, blue: 0) |
| 23 | public static let white = CKeyColor(red: 255, green: 255, blue: 255) |
| 24 | |
| 25 | init(_ c: ckey_rgb) { self.init(red: c.r, green: c.g, blue: c.b) } |
| 26 | var c: ckey_rgb { ckey_rgb(r: red, g: green, b: blue) } |
| 27 | } |
| 28 | |
| 29 | /// Optical-safety policy carried by a style (specification §4). |
| 30 | public enum CKeyStylePolicy: Int, Sendable, CaseIterable { |
| 31 | /// Default: renderers refuse a style that fails the safety rules. |
| 32 | case strict = 0 |
| 33 | /// Renderers repair the style by fixed rules, then apply the strict rules. |
| 34 | case adaptive = 1 |
| 35 | /// Renderers draw the style as given; the caller accepts an uncertified symbol. |
| 36 | case override = 2 |
| 37 | } |
| 38 | |
| 39 | /// The Profile 0 design system (`ckey_style`). Styling changes how a symbol |
| 40 | /// is painted, never where a reader samples; the core checks every style |
| 41 | /// (contrast, effect limits, logo error budget) and refuses unsafe ones |
| 42 | /// under the `.strict` policy. |
| 43 | public struct CKeyStyle: Hashable, Sendable { |
| 44 | public enum ModuleShape: Int, Sendable, CaseIterable { case rounded = 0, square = 1, circle = 2, diamond = 3 } |
| 45 | public enum RingStyle: Int, Sendable, CaseIterable { case joined = 0, dots = 1 } |
| 46 | public enum RayCap: Int, Sendable, CaseIterable { case round = 0, flat = 1 } |
| 47 | |
| 48 | /// Background centre colour. |
| 49 | public var background: CKeyColor |
| 50 | /// Background edge colour (radial vignette); equal to `background` for solid. |
| 51 | public var backgroundEdge: CKeyColor |
| 52 | /// Mark colour; the light side of a metallic sheen. |
| 53 | public var ink: CKeyColor |
| 54 | /// Sheen shade; equal to `ink` for flat ink. |
| 55 | public var inkShade: CKeyColor |
| 56 | /// Up to four accent colours for payload marks. |
| 57 | public var accents: [CKeyColor] |
| 58 | /// 0...100: share of payload marks drawn in accents. |
| 59 | public var accentPercent: Int |
| 60 | public var modules: ModuleShape |
| 61 | public var rings: RingStyle |
| 62 | public var rays: RayCap |
| 63 | /// 0...1 embossed highlight. |
| 64 | public var bevel: Double |
| 65 | /// 0...1 drop-shadow depth. |
| 66 | public var shadow: Double |
| 67 | /// 0...1 background grain. |
| 68 | public var texture: Double |
| 69 | /// Logo image URL or `data:` URI for SVG export; `nil` for none. The |
| 70 | /// raster, PNG and PDF outputs draw only the logo plate. |
| 71 | public var logoHref: String? |
| 72 | /// Logo radius in canonical units; 0 = the empty centre's size. |
| 73 | public var logoRadius: Double |
| 74 | /// Draw a background disc behind the logo. |
| 75 | public var logoPlate: Bool |
| 76 | /// Optical-safety policy; `.strict` by default. |
| 77 | public var policy: CKeyStylePolicy |
| 78 | |
| 79 | /// Black ink on a white background (`ckey_style_default`). |
| 80 | public static var plain: CKeyStyle { preset(CKEY_PRESET_PLAIN) } |
| 81 | /// Cream metallic ink on a dark vignette, with accents, depth and grain. |
| 82 | public static var concept: CKeyStyle { preset(CKEY_PRESET_CONCEPT) } |
| 83 | |
| 84 | /// The default style (same as `.plain`). |
| 85 | public init() { |
| 86 | var c = ckey_style() |
| 87 | ckey_style_default(&c) |
| 88 | self.init(c) |
| 89 | } |
| 90 | |
| 91 | private static func preset(_ preset: ckey_style_preset) -> CKeyStyle { |
| 92 | var c = ckey_style() |
| 93 | ckey_style_default(&c) |
| 94 | _ = ckey_style_preset_fill(&c, preset) |
| 95 | return CKeyStyle(c) |
| 96 | } |
| 97 | |
| 98 | init(_ c: ckey_style) { |
| 99 | background = CKeyColor(c.background) |
| 100 | backgroundEdge = CKeyColor(c.background_edge) |
| 101 | ink = CKeyColor(c.ink) |
| 102 | inkShade = CKeyColor(c.ink_shade) |
| 103 | let count = Int(min(c.accent_count, 4)) |
| 104 | accents = withUnsafeBytes(of: c.accents) { raw in |
| 105 | raw.bindMemory(to: ckey_rgb.self).prefix(count).map { CKeyColor($0) } |
| 106 | } |
| 107 | accentPercent = Int(c.accent_percent) |
| 108 | modules = ModuleShape(rawValue: Int(c.modules.rawValue)) ?? .rounded |
| 109 | rings = RingStyle(rawValue: Int(c.rings.rawValue)) ?? .joined |
| 110 | rays = RayCap(rawValue: Int(c.rays.rawValue)) ?? .round |
| 111 | bevel = c.bevel |
| 112 | shadow = c.shadow |
| 113 | texture = c.texture |
| 114 | logoHref = c.logo_href.map { String(cString: $0) }.flatMap { $0.isEmpty ? nil : $0 } |
| 115 | logoRadius = c.logo_radius |
| 116 | logoPlate = c.logo_plate != 0 |
| 117 | policy = CKeyStylePolicy(rawValue: Int(c.policy)) ?? .strict |
| 118 | } |
| 119 | |
| 120 | /// Calls `body` with the equivalent `ckey_style`; the logo string is |
| 121 | /// valid only inside `body`. Values that do not fit the C fields are |
| 122 | /// clamped so the core's own checks report them. |
| 123 | func withCStyle<R>(_ body: (UnsafePointer<ckey_style>) throws -> R) rethrows -> R { |
| 124 | var c = ckey_style() |
| 125 | ckey_style_default(&c) // fills every field of the current ABI first |
| 126 | c.background = background.c |
| 127 | c.background_edge = backgroundEdge.c |
| 128 | c.ink = ink.c |
| 129 | c.ink_shade = inkShade.c |
| 130 | var slots = accents.prefix(4).map(\.c) |
| 131 | let accentCount = slots.count |
| 132 | while slots.count < 4 { slots.append(ckey_rgb(r: 0, g: 0, b: 0)) } |
| 133 | c.accents = (slots[0], slots[1], slots[2], slots[3]) |
| 134 | c.accent_count = UInt32(accentCount) |
| 135 | c.accent_percent = UInt32(clamping: accentPercent) |
| 136 | c.modules = ckey_module_shape(rawValue: UInt32(modules.rawValue)) |
| 137 | c.rings = ckey_ring_style(rawValue: UInt32(rings.rawValue)) |
| 138 | c.rays = ckey_ray_cap(rawValue: UInt32(rays.rawValue)) |
| 139 | c.bevel = bevel |
| 140 | c.shadow = shadow |
| 141 | c.texture = texture |
| 142 | c.logo_radius = logoRadius |
| 143 | c.logo_plate = logoPlate ? 1 : 0 |
| 144 | c.policy = UInt32(policy.rawValue) |
| 145 | if let logoHref, !logoHref.isEmpty { |
| 146 | return try logoHref.withCString { href in |
| 147 | c.logo_href = href |
| 148 | return try withUnsafePointer(to: &c) { try body($0) } |
| 149 | } |
| 150 | } |
| 151 | c.logo_href = nil |
| 152 | return try withUnsafePointer(to: &c) { try body($0) } |
| 153 | } |
| 154 | } |