import CoreText
import SwiftUI
import UniformTypeIdentifiers
private func hex(_ c: Color, _ scheme: ColorScheme) -> String {
var env = EnvironmentValues()
env.colorScheme = scheme
let comps = c.resolve(in: env).cgColor.converted(to: CGColorSpace(name: CGColorSpace.sRGB)!, intent: .defaultIntent, options: nil)!.components!
return "#" + comps.prefix(3).map { String(Int(($0 * 255).rounded()) + 0x100, radix: 16).dropFirst() }.joined()
}
private func num(_ v: CGFloat) -> String {
"\((v * 10).rounded() / 10)"
}
private func esc(_ s: String) -> String {
s.replacingOccurrences(of: "&", with: "&").replacingOccurrences(of: "<", with: "<").replacingOccurrences(of: ">", with: ">").replacingOccurrences(of: "\"", with: """)
}
private func json(_ v: Any) -> String {
String(decoding: try! JSONSerialization.data(withJSONObject: v), as: UTF8.self).replacingOccurrences(of: "", with: "<\\/")
}
private func fontFace(_ subset: String) -> String {
let url = Bundle.main.resourceURL!.appending(path: "fonts/atkinson-hyperlegible-next-\(subset)-wght-normal.woff2")
let data = try! Data(contentsOf: url)
return "@font-face{font-family:Atkinson;font-weight:200 800;src:url(data:font/woff2;base64,\(data.base64EncodedString())) format(\"woff2\")}"
}
private func edgePath(_ pts: [CGPoint]) -> String {
guard let first = pts.first else {
return ""
}
var d = "M\(num(first.x)) \(num(first.y))"
if pts.count > 2 {
for i in 1..<(pts.count - 1) {
let a = pts[i - 1]
let b = pts[i]
let c = pts[i + 1]
let lin = hypot(b.x - a.x, b.y - a.y)
let lout = hypot(c.x - b.x, c.y - b.y)
let r = min(6, lin / 2, lout / 2)
let p = CGPoint(x: b.x - (b.x - a.x) / lin * r, y: b.y - (b.y - a.y) / lin * r)
let q = CGPoint(x: b.x + (c.x - b.x) / lout * r, y: b.y + (c.y - b.y) / lout * r)
d += "L\(num(p.x)) \(num(p.y))Q\(num(b.x)) \(num(b.y)) \(num(q.x)) \(num(q.y))"
}
}
let last = pts[pts.count - 1]
return d + "L\(num(last.x)) \(num(last.y))"
}
private func arrowHead(_ from: CGPoint, _ to: CGPoint) -> String {
let angle = atan2(to.y - from.y, to.x - from.x)
let size: CGFloat = 8
let a = CGPoint(x: to.x - size * cos(angle - .pi / 7), y: to.y - size * sin(angle - .pi / 7))
let b = CGPoint(x: to.x - size * cos(angle + .pi / 7), y: to.y - size * sin(angle + .pi / 7))
return "\(num(to.x)),\(num(to.y)) \(num(a.x)),\(num(a.y)) \(num(b.x)),\(num(b.y))"
}
@MainActor
func diagramHTML(_ layout: BoxLayout) -> String {
let colors: [(String, Color)] = [("bg", Palette.bg), ("border", Palette.border), ("text", Palette.text), ("muted", Palette.muted), ("accent", Palette.accent)]
let light = colors.map { "--\($0.0):\(hex($0.1, .light))" }.joined(separator: ";")
let dark = colors.map { "--\($0.0):\(hex($0.1, .dark))" }.joined(separator: ";")
let titleLine = textSize("X", titleFont).height
let detailLine = textSize("X", detailFont).height
var svg = ""
for b in layout.boxes {
let r = b.rect
let f = fill(b)
svg += "