Module: Echoes::SvgSniffer
- Defined in:
- lib/echoes/svg_sniffer.rb
Overview
Detect SVG payloads in arbitrary image bytes. Used by the Kitty graphics and iTerm2 inline-image decoders to fork off to the SvgRenderer instead of NSBitmapImageRep (which is raster-only).
Constant Summary collapse
- SVG_RE =
Skip optional UTF-8 BOM, whitespace, an XML prologue, and a DOCTYPE before the ‘<svg` element. Case-insensitive so `<SVG` and `<Svg` both match. `n` flag forces ASCII-8BIT so we can match against the binary payloads the decoders hand us.
/\A(?:\xEF\xBB\xBF)?\s*(?:<\?xml[^>]*\?>\s*)?(?:<!DOCTYPE\s+svg[^>]*>\s*)?<svg\b/imn- OPEN_SVG_RE =
Capture the leading ‘<svg …>` opening tag so intrinsic_size can work on just the attribute soup, ignoring the rest of the document.
/<svg\b([^>]*)>/imn- DIM_RE =
Numeric attribute on the SVG root, optionally suffixed with a CSS length unit. We extract the number; unit conversion is best-effort (px / pt / unitless treated as pixels; em / % yield nil because they need layout context we don’t have).
/\A\s*([0-9]*\.?[0-9]+)\s*([a-z%]*)\s*\z/i
Class Method Summary collapse
- .extract_attr(attrs, name) ⇒ Object
-
.intrinsic_size(bytes) ⇒ Object
Returns [width_px, height_px] or nil.
- .parse_dim(str) ⇒ Object
- .svg?(bytes) ⇒ Boolean
Class Method Details
.extract_attr(attrs, name) ⇒ Object
62 63 64 65 |
# File 'lib/echoes/svg_sniffer.rb', line 62 def extract_attr(attrs, name) m = attrs.match(/\s#{name}\s*=\s*["']([^"']*)["']/i) m && m[1] end |
.intrinsic_size(bytes) ⇒ Object
Returns [width_px, height_px] or nil. Parses the <svg> tag’s width/height attributes first; falls back to viewBox dimensions if either is missing or non-pixel.
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/echoes/svg_sniffer.rb', line 35 def intrinsic_size(bytes) return nil if bytes.nil? || bytes.empty? head = bytes.byteslice(0, 4096).b m = OPEN_SVG_RE.match(head) return nil unless m attrs = m[1].to_s w = parse_dim(extract_attr(attrs, 'width')) h = parse_dim(extract_attr(attrs, 'height')) if w.nil? || h.nil? vb = extract_attr(attrs, 'viewBox') if vb parts = vb.strip.split(/[\s,]+/) if parts.size == 4 vw = parts[2].to_f vh = parts[3].to_f w ||= vw if vw.positive? h ||= vh if vh.positive? end end end return nil unless w && h && w.positive? && h.positive? [w.round, h.round] end |
.parse_dim(str) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/echoes/svg_sniffer.rb', line 67 def parse_dim(str) return nil if str.nil? || str.empty? m = DIM_RE.match(str) return nil unless m unit = m[2].downcase # Unitless / px / pt are treated as pixels (pt is technically # 1.333px but the difference is invisible at typical SVG sizes # and we'd be guessing the renderer's DPI anyway). em / % need # layout context, so we punt. return nil if unit == 'em' || unit == '%' n = m[1].to_f n.positive? ? n : nil end |
.svg?(bytes) ⇒ Boolean
26 27 28 29 30 |
# File 'lib/echoes/svg_sniffer.rb', line 26 def svg?(bytes) return false if bytes.nil? || bytes.empty? head = bytes.byteslice(0, 4096).b SVG_RE.match?(head) end |