110 lines
4.1 KiB
Swift
110 lines
4.1 KiB
Swift
import AppKit
|
||||
|
|
import CoreText
|
|||
|
|
import SwiftUI
|
|||
|
|
|
|||
|
|
func runFonts(_ font: CTFont, _ text: String) -> String {
|
|||
|
|
let line = CTLineCreateWithAttributedString(NSAttributedString(string: text, attributes: [.font: font as NSFont]))
|
|||
|
|
return (CTLineGetGlyphRuns(line) as! [CTRun]).map { run in
|
|||
|
|
let f = (CTRunGetAttributes(run) as NSDictionary)[kCTFontAttributeName] as! CTFont
|
|||
|
|
let file = (CTFontCopyAttribute(f, kCTFontURLAttribute) as? URL)?.lastPathComponent ?? "system"
|
|||
|
|
let r = CTRunGetStringRange(run)
|
|||
|
|
let part = (text as NSString).substring(with: NSRange(location: r.location, length: r.length))
|
|||
|
|
return "\(part) → \(CTFontCopyPostScriptName(f)) (\(file))"
|
|||
|
|
}.joined(separator: "\n")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
struct FontSpike: View {
|
|||
|
|
private let sample = "The quick brown fox jumps — 0O 1lI {}[] őŁżę → │"
|
|||
|
|
|
|||
|
|
var body: some View {
|
|||
|
|
ScrollView {
|
|||
|
|
VStack(alignment: .leading, spacing: 12) {
|
|||
|
|
ForEach(Array([(400.0, false), (700.0, false), (400.0, true), (700.0, true)].enumerated()), id: \.offset) { _, style in
|
|||
|
|
let (w, italic) = style
|
|||
|
|
VStack(alignment: .leading) {
|
|||
|
|
Text("weight \(Int(w))\(italic ? " italic" : "")")
|
|||
|
|
.foregroundStyle(.secondary)
|
|||
|
|
Text(sample)
|
|||
|
|
.font(Fonts.font(16, weight: w, italic: italic))
|
|||
|
|
Text(runFonts(Fonts.ct(16, weight: w, italic: italic), sample))
|
|||
|
|
.foregroundStyle(.secondary)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
VStack(alignment: .leading) {
|
|||
|
|
Text("Fonts.font(16) with SwiftUI .bold() and .italic()")
|
|||
|
|
.foregroundStyle(.secondary)
|
|||
|
|
Text(sample)
|
|||
|
|
.font(Fonts.font(16))
|
|||
|
|
.bold()
|
|||
|
|
.italic()
|
|||
|
|
}
|
|||
|
|
VStack(alignment: .leading) {
|
|||
|
|
Text("System font, for comparison")
|
|||
|
|
.foregroundStyle(.secondary)
|
|||
|
|
Text(sample)
|
|||
|
|
.font(.system(size: 16))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|||
|
|
.padding(8)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
struct SVGSpike: View {
|
|||
|
|
@Environment(Server.self) private var server
|
|||
|
|
@ViewState private var path = "web/favicon.svg"
|
|||
|
|
@ViewState private var image: NSImage?
|
|||
|
|
@ViewState private var failure = ""
|
|||
|
|
|
|||
|
|
var body: some View {
|
|||
|
|
VStack(alignment: .leading) {
|
|||
|
|
HStack {
|
|||
|
|
TextField("Worktree path", text: $path)
|
|||
|
|
.frame(width: 320)
|
|||
|
|
Button("Load") {
|
|||
|
|
Task {
|
|||
|
|
await load()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if let image {
|
|||
|
|
Text(verbatim: "NSImage size \(Int(image.size.width))×\(Int(image.size.height)), reps \(image.representations.map { String(describing: type(of: $0)) })")
|
|||
|
|
.foregroundStyle(.secondary)
|
|||
|
|
HStack(alignment: .top, spacing: 16) {
|
|||
|
|
Image(nsImage: image)
|
|||
|
|
Image(nsImage: image)
|
|||
|
|
.resizable()
|
|||
|
|
.scaledToFit()
|
|||
|
|
.frame(width: 256, height: 256)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if !failure.isEmpty {
|
|||
|
|
Text(failure)
|
|||
|
|
}
|
|||
|
|
Spacer()
|
|||
|
|
}
|
|||
|
|
.task {
|
|||
|
|
await load()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private func load() async {
|
|||
|
|
image = nil
|
|||
|
|
failure = ""
|
|||
|
|
guard !server.taskId.isEmpty else {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
do {
|
|||
|
|
let data = try await server.data(URLRequest(url: try server.url("/api/tasks/\(server.taskId)/image", [URLQueryItem(name: "path", value: path)])))
|
|||
|
|
guard let img = NSImage(data: data) else {
|
|||
|
|
failure = "image not available: \(path)"
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
image = img
|
|||
|
|
} catch {
|
|||
|
|
failure = "image not available: \(path) (\(error))"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|