#if os(macOS) import AppKit import AutoCatCore import SwiftUI extension CGRect { func inset(by insets: NSEdgeInsets) -> CGRect { CGRect(origin: CGPoint(x: origin.x + insets.left, y: origin.y + insets.bottom), size: CGSize(width: size.width - insets.left - insets.right, height: size.height - insets.top - insets.bottom)) } } extension NSScreen { static var mainScreenScale: CGFloat { main?.backingScaleFactor ?? 1 } } class FlagLayer: CALayer { let pixelWidth = 1/NSScreen.mainScreenScale // Flag colors - https://ru.wikipedia.org/wiki/%D0%A4%D0%BB%D0%B0%D0%B3_%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D0%B8 let blue = NSColor(srgbRed: 0, green: 57/256.0, blue: 166/256.0, alpha: 1) let red = NSColor(srgbRed: 213/256.0, green: 43/256.0, blue: 30/256.0, alpha: 1) override func draw(in ctx: CGContext) { ctx.saveGState() super.draw(in: ctx) ctx.setStrokeColor(NSColor.black.cgColor) ctx.setLineWidth(0) ctx.setFillColor(NSColor.white.cgColor) ctx.fill(bounds.inset(by: NSEdgeInsets(top: 0, left: 0, bottom: bounds.height*2/3, right: 0))) ctx.setFillColor(self.blue.cgColor) ctx.fill(bounds.insetBy(dx: 0, dy: bounds.height/3)) ctx.setFillColor(self.red.cgColor) ctx.fill(bounds.inset(by: NSEdgeInsets(top: bounds.height*2/3, left: 0, bottom: 0, right: 0))) ctx.setLineWidth(pixelWidth) ctx.stroke(bounds.insetBy(dx: pixelWidth/2, dy: pixelWidth/2)) ctx.restoreGState() } } class PlateView: NSView { // Some driver plate parameters from "ГОСТ Р 50577-93" // http://docs.cntd.ru/document/gost-r-50577-93 private static let aspectRatio: CGFloat = 112.0/520.0 private static let fontHeightCoeff: CGFloat = 58.0/76.0 private var bgLayer = CALayer() private var mainBgLayer = CALayer() private var regionBgLayer = CALayer() private var numberLayer = CenterTextLayer(coeff: fontHeightCoeff) private var regionLayer = CenterTextLayer(coeff: fontHeightCoeff) private var countryLayer = CenterTextLayer() private var flagLayer = FlagLayer() var number: PlateNumber? { didSet { self.layout() self.setAccessibilityLabel(number?.asString()) } } var foreground: NSColor? { didSet { self.layout() } } var fontSize: CGFloat = 10 { didSet { self.layout() } } var onChange: (() -> Void)? override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } func setup() { self.wantsLayer = true self.layer?.backgroundColor = NSColor.clear.cgColor self.bgLayer.cornerRadius = 6 //self.bounds.height/8 self.layer?.addSublayer(self.bgLayer) self.mainBgLayer.cornerRadius = 4 self.layer?.addSublayer(self.mainBgLayer) self.regionBgLayer.cornerRadius = 4 self.layer?.addSublayer(self.regionBgLayer) self.numberLayer.alignmentMode = .center self.numberLayer.contentsScale = NSScreen.mainScreenScale self.layer?.addSublayer(self.numberLayer) self.regionLayer.alignmentMode = .center self.regionLayer.contentsScale = NSScreen.mainScreenScale self.layer?.addSublayer(self.regionLayer) self.countryLayer.contentsScale = NSScreen.mainScreenScale self.countryLayer.string = "RUS" self.countryLayer.alignmentMode = .center self.layer?.addSublayer(self.countryLayer) self.flagLayer.contentsScale = NSScreen.mainScreenScale self.layer?.addSublayer(self.flagLayer) self.setAccessibilityElement(true) } private func pathForFlag(in rect: CGRect) -> CGPath { let path = CGMutablePath() let rect = CGPath(rect: rect, transform: nil) path.addPath(rect) return path } override func layout() { super.layout() guard let number = self.number else { return } guard let fgColorMain = NSColor(named: "PlateForeground")?.cgColor else { return } guard let bgColor = NSColor(named: "PlateBackground")?.cgColor else { return } let fgColor = self.foreground?.cgColor ?? fgColorMain self.bgLayer.backgroundColor = fgColor self.bgLayer.frame = self.bounds self.mainBgLayer.backgroundColor = bgColor self.regionBgLayer.backgroundColor = bgColor self.mainBgLayer.frame = self.bounds.inset(by: NSEdgeInsets(top: 2, left: 2, bottom: 2, right: self.bounds.width*0.27 + 1)) self.regionBgLayer.frame = self.bounds.inset(by: NSEdgeInsets(top: 2, left: self.bounds.width*0.73 + 1, bottom: 2, right: 2)) self.numberLayer.frame = self.mainBgLayer.frame.insetBy(dx: 4, dy: 0) let font = NSFont(name: "RoadNumbers", size: self.mainBgLayer.frame.height*1.1) ?? NSFont.boldSystemFont(ofSize: 24) let attributes: [NSAttributedString.Key: Any] = [ .kern: 3, .font: font, .foregroundColor: fgColor ] let attributed = NSAttributedString(string: number.mainPart(), attributes: attributes) self.numberLayer.string = attributed let rbgSize = self.regionBgLayer.frame.size self.regionLayer.frame = self.regionBgLayer.frame.inset(by: NSEdgeInsets(top: 2, left: 2, bottom: rbgSize.height*0.35, right: 2)) let regionFont = NSFont(name: "RoadNumbers", size: rbgSize.height*0.8) ?? NSFont.boldSystemFont(ofSize: 24) let regionAttrs: [NSAttributedString.Key: Any] = [ .kern: 1, .font: regionFont, .foregroundColor: fgColor ] let attributedRegion = NSAttributedString(string: number.region(), attributes: regionAttrs) self.regionLayer.string = attributedRegion self.countryLayer.foregroundColor = fgColor self.countryLayer.frame = self.regionBgLayer.frame.inset(by: NSEdgeInsets(top: rbgSize.height*0.64, left: rbgSize.width*0.08, bottom: rbgSize.height*0.07, right: rbgSize.width*0.42)) self.countryLayer.fontSize = self.countryLayer.frame.size.height let top = (self.regionBgLayer.frame.origin.y + rbgSize.height*0.04).rounded(.toNearestOrAwayFromZero) let left = (self.regionBgLayer.frame.origin.x + rbgSize.width*0.62).rounded(.toNearestOrAwayFromZero) let w = (rbgSize.width*0.34).rounded(.toNearestOrAwayFromZero) let h = (rbgSize.height*0.28).rounded(.toNearestOrAwayFromZero) self.flagLayer.frame = CGRect(x: left, y: top, width: w, height: h) self.flagLayer.setNeedsDisplay() } override var intrinsicContentSize: CGSize { let height = fontSize/1.1 + 4 let width = height/PlateView.aspectRatio return CGSize(width: width, height: height) } } struct PlateNumberView: NSViewRepresentable { var number: PlateNumber var unrecognized: Bool var outdated: Bool var fontSize: CGFloat func makeNSView(context: Context) -> PlateView { let view = PlateView(frame: .zero) view.fontSize = fontSize return view } func updateNSView(_ nsView: PlateView, context: Context) { nsView.number = number nsView.setContentHuggingPriority(.defaultHigh, for: .horizontal) nsView.setContentHuggingPriority(.defaultHigh, for: .vertical) } } struct PlateNumberView_Previews: PreviewProvider { static var previews: some View { Group { PlateNumberView(number: PlateNumber("Е201АМ761"), unrecognized: false, outdated: false, fontSize: 50) PlateNumberView(number: PlateNumber("Е201АМ761"), unrecognized: true, outdated: false, fontSize: 50) PlateNumberView(number: PlateNumber("Е201АМ761"), unrecognized: false, outdated: true, fontSize: 50) } } } #endif