import AppKit // MARK: - LucideIcons // // Vendored [Lucide](https://lucide.dev) icons, used for callout headers and // Read-mode checkboxes. We vendor the SVG markup (rather than SF Symbols) // because Read mode / PDF export *redistributes* the rendered icons, and the SF // Symbols license forbids distributing those symbols in print form. Lucide is // ISC-licensed (a few icons MIT, via Feather) — both permit redistribution; the // notices live in `LICENSES/lucide.txt`. // // Only each icon's inner geometry is stored; `inlineSVG`/`image` wrap it in a // 24×24, stroke-based `` matching Lucide's canonical form. One source feeds // both back-ends: Read mode inlines the SVG (vector, CSS-tinted via // `currentColor`); Edit mode rasterizes it to a tinted `NSImage` overlay. enum LucideIcons { /// Lucide icon id → inner SVG geometry, verbatim from lucide.dev (v ISC). /// Keys match `CalloutStyle.iconName` plus the checkbox primitives. static let geometry: [String: String] = [ "pencil": #""#, "flame": #""#, "message-square-warning": #""#, "triangle-alert": #""#, "octagon-alert": #""#, "clipboard-list": #""#, "info": #""#, "circle-dashed": #""#, "check": #""#, "circle-question-mark": #""#, "x": #""#, "zap": #""#, "bug": #""#, "list": #""#, "quote": #""#, "circle": #""#, "image-off": #""#, ] /// Raw `…` with `stroke="currentColor"` for inlining into HTML; /// the host CSS supplies the color. Returns `nil` for an unknown id. static func inlineSVG(_ name: String) -> String? { guard let g = geometry[name] else { return nil } return strokeSVG(geometry: g, stroke: "currentColor") } /// An `NSImage` of the icon stroked in `color`, sized to a `pointSize` /// square. Renders the SVG (in black) then tints with `.sourceIn` so the /// glyph matches `color` exactly regardless of the SVG decoder's color space /// — the same technique the PDF icon path used. `sourceIn` (not /// `sourceAtop`) matters when `color` is itself translucent (e.g. a dynamic /// system color like `.secondaryLabelColor`): `sourceIn`'s result alpha is /// `color.alpha * baseGlyphAlpha`, so the tint's own translucency survives; /// `sourceAtop` keeps only the base glyph's alpha, silently discarding the /// tint's alpha — invisible with the opaque theme colors this was first /// used with, but it flattens a translucent tint to solid opaque. `nil` for /// an unknown id or if the platform SVG decoder can't build the image. static func image(_ name: String, color: NSColor, pointSize: CGFloat) -> NSImage? { guard let g = geometry[name], let data = strokeSVG(geometry: g, stroke: "#000000").data(using: .utf8), let base = NSImage(data: data) else { return nil } base.cacheMode = .never // re-rasterize the SVG at each draw scale (crisp on Retina) let box = NSSize(width: pointSize, height: pointSize) let image = NSImage(size: box, flipped: false) { rect in base.draw(in: rect) color.setFill() NSGraphicsContext.current?.cgContext.setBlendMode(.sourceIn) rect.fill() return true } image.cacheMode = .never return image } /// The icon's stroke geometry as a CGPath in Lucide's canonical 24×24, /// y-down viewBox space (stroke it with width 2, round caps/joins, to /// match the rendered SVG). Used where the icon must be drawn as a /// *shape*, not an image — an image on a wrapping TextKit 2 fragment /// wedges its layout to one line (see FragmentOverlay). `nil` for an /// unknown id. static func path(_ name: String) -> CGPath? { guard let g = geometry[name] else { return nil } return SVGPath.path(fromGeometry: g) } /// Read-mode checkbox markup mirroring the editor's look. Unchecked: a /// stroked `circle`. Checked: a disc filled in `currentColor` (CSS supplies /// the accent) with a white check on top. The themeable part uses /// `currentColor`; the check is a literal white so it reads on the disc. static func checkboxSVG(checked: Bool) -> String { if checked { return ##""## } return #""# } /// Wraps inner `geometry` in Lucide's canonical stroke-based ``. private static func strokeSVG(geometry: String, stroke: String) -> String { #""# + geometry + "" } }