using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.Svg; using System; using System.Numerics; using Windows.Foundation; using Windows.UI; namespace Sonex.Client.Controls.SceneCanvas; public sealed class SceneCanvasSvgObject : SceneCanvasObject { public const string DefaultSvgCode = """ """; private string _svgCode = DefaultSvgCode; private CanvasSvgDocument? _cachedDocument; private ICanvasResourceCreator? _cachedResourceCreator; private string _cachedSvgCode = string.Empty; public SceneCanvasSvgObject(Rect boundsMeters, string? svgCode = null) : base(boundsMeters) { CanMove = true; CanResize = true; CanRotate = true; SvgCode = svgCode ?? DefaultSvgCode; } public string SvgCode { get => _svgCode; set { var normalized = NormalizeSvgCode(value); if (string.Equals(_svgCode, normalized, StringComparison.Ordinal)) { return; } _svgCode = normalized; ResetSvgCache(); } } public override SceneCanvasObject Clone() { var clone = new SceneCanvasSvgObject(BoundsMeters, SvgCode); CopyBasePropertiesTo(clone); return clone; } public override void OnDraw(SceneCanvasDrawContext context) { var width = (float)BoundsMeters.Width; var height = (float)BoundsMeters.Height; if (width <= 0f || height <= 0f) { return; } var drawingSession = context.DrawingSession; var x = (float)BoundsMeters.X; var y = (float)BoundsMeters.Y; var previousTransform = drawingSession.Transform; if (Math.Abs(RotationRadians) > 0.0001d) { var center = new Vector2( (float)(BoundsMeters.X + (BoundsMeters.Width * 0.5d)), (float)(BoundsMeters.Y + (BoundsMeters.Height * 0.5d))); var localRotation = Matrix3x2.CreateRotation((float)RotationRadians, center); drawingSession.Transform = localRotation * previousTransform; } if (TryEnsureSvgDocument(drawingSession) && _cachedDocument != null) { drawingSession.DrawSvg(_cachedDocument, new Size(width, height), x, y); } else { // Invalid SVG input fallback: visible placeholder with clear error cue. var fallbackFill = Color.FromArgb(64, 210, 35, 35); var fallbackStroke = Color.FromArgb(255, 210, 35, 35); var stroke = MathF.Max(0.02f, 1f / context.ZoomPixelsPerMeter); drawingSession.FillRectangle(x, y, width, height, fallbackFill); drawingSession.DrawRectangle(x, y, width, height, fallbackStroke, stroke); drawingSession.DrawLine(x, y, x + width, y + height, fallbackStroke, stroke); drawingSession.DrawLine(x + width, y, x, y + height, fallbackStroke, stroke); } if (!previousTransform.Equals(drawingSession.Transform)) { drawingSession.Transform = previousTransform; } } private bool TryEnsureSvgDocument(ICanvasResourceCreator resourceCreator) { if (ReferenceEquals(_cachedResourceCreator, resourceCreator) && string.Equals(_cachedSvgCode, _svgCode, StringComparison.Ordinal)) { return _cachedDocument != null; } _cachedDocument?.Dispose(); _cachedDocument = null; _cachedResourceCreator = resourceCreator; _cachedSvgCode = _svgCode; try { if (!CanvasSvgDocument.IsSupported(resourceCreator.Device)) { return false; } _cachedDocument = CanvasSvgDocument.LoadFromXml(resourceCreator, _svgCode); return _cachedDocument != null; } catch { _cachedDocument = null; return false; } } private static string NormalizeSvgCode(string? svgCode) { if (string.IsNullOrWhiteSpace(svgCode)) { return DefaultSvgCode; } return svgCode.Trim(); } private void ResetSvgCache() { _cachedDocument?.Dispose(); _cachedDocument = null; _cachedResourceCreator = null; _cachedSvgCode = string.Empty; } }