Multi-platform .NET bindings to the Ultralight project.
1using System; 2using System.Diagnostics; 3using System.IO; 4using System.Runtime.InteropServices; 5using System.Text; 6using System.Threading; 7using SixLabors.ImageSharp.PixelFormats; 8using ImpromptuNinjas.UltralightSharp.Enums; 9 10namespace ImpromptuNinjas.UltralightSharp.Demo { 11 12 public static partial class DemoProgram { 13 14 public static void Main(string[] args) { 15 var isNotInteractive = !Environment.UserInteractive || Console.IsInputRedirected; 16 17 // setup logging 18 Safe.LoggerLogMessageCallback cb = LoggerCallback; 19 Safe.Ultralight.SetLogger(new Safe.Logger {LogMessage = cb}); 20 21 if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { 22 Console.OutputEncoding = Encoding.UTF8; 23 Ansi.WindowsConsole.TryEnableVirtualTerminalProcessing(); 24 } 25 26 var asmPath = new Uri(typeof(DemoProgram).Assembly.CodeBase!).LocalPath; 27 var asmDir = Path.GetDirectoryName(asmPath)!; 28 var tempDir = Path.GetTempPath(); 29 // find a place to stash instance storage 30 string storagePath; 31 do 32 storagePath = Path.Combine(tempDir, Guid.NewGuid().ToString()); 33 while (Directory.Exists(storagePath) || File.Exists(storagePath)); 34 35 { 36 using var cfg = new Safe.Config(); 37 38 var cachePath = Path.Combine(storagePath, "Cache"); 39 cfg.SetCachePath(cachePath); 40 41 var resourcePath = Path.Combine(asmDir, "resources"); 42 cfg.SetResourcePath(resourcePath); 43 44 cfg.SetUseGpuRenderer(false); 45 cfg.SetEnableImages(true); 46 cfg.SetEnableJavaScript(true); 47 48 Safe.AppCore.EnablePlatformFontLoader(); 49 50 { 51 var assetsPath = Path.Combine(asmDir, "assets"); 52 Safe.AppCore.EnablePlatformFileSystem(assetsPath); 53 } 54 55 using var renderer = new Safe.Renderer(cfg); 56 var sessionName = "Demo"; 57 using var session = new Safe.Session(renderer, false, sessionName); 58 59 using var view = new Safe.View(renderer, 640, 480, false, session); 60 61 //var htmlString = "<i>Loading...</i>"; 62 //Console.WriteLine($"Loading HTML: {htmlString}"); 63 //view.LoadHtml(htmlString); 64 view.LoadUrl("file:///index.html"); 65 66 var nextStep = false; 67 68 view.SetFinishLoadingCallback((data, caller, frameId, isMainFrame, url) => { 69 //Console.WriteLine($"Loading Finished, URL: {url}"); 70 71 nextStep = true; 72 }, default); 73 74 if (isNotInteractive) 75 do { 76 renderer.Update(); 77 renderer.Render(); 78 } while (nextStep == false); 79 80 const double wm = 4; 81 const double hm = wm * 2; 82 83 GetConsoleSize(out var cw, out var ch); 84 view.Resize((uint) (cw * wm), (uint) (ch * hm)); 85 86 var ditherIndex = 0; 87 var customDither = Dithers[ditherIndex]; 88 89 var ansiColors = (AnsiColors) 0; 90 91 var step = 0; 92 var escInstrBytes = Encoding.UTF8.GetBytes("Press C to change color mode, D to change dither mode, ESC to exit."); 93 var key = new ConsoleKeyInfo(); 94 //Console.TreatControlCAsInput = true; 95 96 using var stdOut = Console.OpenStandardOutput(256); 97 using var o = new BufferedStream(stdOut, 8 * 1024 * 1024); 98 99 byte[]? urlStrBytes = null; 100 // alt buffer 101 if (!isNotInteractive) { 102 o.WriteByte(0x1B); 103 o.WriteByte((byte) '['); 104 o.WriteByte((byte) '1'); 105 o.WriteByte((byte) '0'); 106 o.WriteByte((byte) '4'); 107 o.WriteByte((byte) '9'); 108 o.WriteByte((byte) 'h'); 109 o.Flush(); 110 } 111 112 try { Console.CursorVisible = false; } 113 catch { 114 /* ok */ 115 } 116 117 try { Console.Clear(); } 118 catch { 119 /* ok */ 120 } 121 122 try { 123 Console.SetCursorPosition(0, 0); 124 } 125 catch { 126 o.WriteByte(0x1B); 127 o.WriteByte((byte) '7'); 128 } 129 130 do { 131 GetConsoleSize(out var newCw, out var newCh); 132 if (cw != newCw || ch != newCh) { 133 cw = newCw; 134 ch = newCh; 135 136 try { Console.Clear(); } 137 catch { 138 /* ok */ 139 } 140 141 view.Resize((uint) (cw * wm), (uint) (ch * hm)); 142 } 143 144 try { 145 Console.SetCursorPosition(0, 0); 146 } 147 catch { 148 o.WriteByte(0x1B); 149 o.WriteByte((byte) '8'); 150 } 151 152 var urlStr = view.GetUrl(); 153 var urlStrSize = Encoding.UTF8.GetByteCount(urlStr ?? ""); 154 if (urlStrBytes == null || urlStrBytes.Length < urlStrSize) 155 urlStrBytes = new byte[urlStrSize]; 156 var urlStrBytesLen = Encoding.UTF8.GetBytes(urlStr, urlStrBytes); 157 o.Write(urlStrBytes, 0, urlStrBytesLen); 158 o.WriteByte((byte) '\n'); 159 160 renderer.Update(); 161 renderer.Render(); 162 163 var surface = view.GetSurface(); 164 var bitmap = surface.GetBitmap(); 165 var pixels = bitmap.LockPixels(); 166 RenderAnsi<Bgra32>(o, pixels, 167 bitmap.GetWidth(), bitmap.GetHeight(), 168 2, 169 borderless: true, 170 colors: ansiColors, // isNotInteractive 171 customDither: customDither 172 ); 173 bitmap.UnlockPixels(); 174 175 if (isNotInteractive) 176 return; 177 178 o.Write(escInstrBytes); 179 o.Flush(); 180 181 if (nextStep) 182 switch (step) { 183 case 0: { 184 view.EvaluateScript("rotateBgColor()"); 185 nextStep = false; 186 ++step; 187 // ReSharper disable once ObjectCreationAsStatement 188 new Timer(state => { 189 nextStep = true; 190 }, null, 10000, Timeout.Infinite); 191 break; 192 } 193 case 1: { 194 var urlString = "https://cristurm.github.io/nyan-cat/"; 195 view.LoadUrl(urlString); 196 nextStep = false; 197 ++step; 198 break; 199 } 200 case 2: 201 view.EvaluateScript( 202 "const m = document.createElement('marquee');" 203 + "const d = document.createElement('div');" 204 + "document.body.appendChild(d);" 205 + "d.appendChild(m);" 206 + "m.setAttribute('width', '100%');" 207 + "m.setAttribute('scrollamount', 4);" 208 + "m.setAttribute('scrolldelay', 1);" 209 + "m.setAttribute('truespeed', 1);" 210 + "m.innerText = 'UltralightSharp';" 211 + "m.style.cssText = '" 212 + "position: absolute;" 213 + "bottom: 0;" 214 + "left: 0;" 215 + "z-index: 999;" 216 + "padding: 4px 8px;" 217 + "font-size: 2.5em;" 218 + "font-weight: bold;" 219 + "width: 100vw;" 220 + "color: #fff" 221 + "';"); 222 nextStep = false; 223 ++step; 224 break; 225 } 226 227 if (!Console.KeyAvailable) 228 continue; 229 230 key = Console.ReadKey(true); 231 Console.WriteLine(); 232 if (key.Key == ConsoleKey.D) { 233 ditherIndex = (ditherIndex + 1) % Dithers.Length; 234 customDither = Dithers[ditherIndex]; 235 continue; 236 } 237 238 if (key.Key == ConsoleKey.C) { 239 ansiColors = (AnsiColors) (((int) ansiColors + 1) % 3); 240 continue; 241 } 242 } while (key.Key != ConsoleKey.Escape); 243 } 244 245 try { 246 Directory.Delete(storagePath, true); 247 } 248 catch { 249 /* ok */ 250 } 251 } 252 253 private static void LoggerCallback(LogLevel logLevel, string? msg) { 254 Debug.WriteLine($"{logLevel.ToString()}: {msg}"); 255 } 256 257 } 258 259}