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

Size
6.2 KB
Lines
154
Language
swift
SHA-256
2ca75f718965…
View on GitHub

Sign in with GitHub to download. Each download is recorded with your GitHub username.

1import CKeyCodec
2import CKeyScanKit
3import Foundation
4
5/// An sRGB colour, 8 bits per channel.
6public 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).
30public 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.
43public 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}