XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Text.Json;
using AngleSharp.Dom;
using Cosmos.Kernel.System.Graphics.Fonts;
using XFESystem.Browser;
using XFESystem.Documents;
using XFESystem.Kernel;

System.Runtime.Loader.AssemblyLoadContext.Default.Resolving+=(_,name)=>{string path=Path.Combine(AppContext.BaseDirectory,name.Name+".dll");return File.Exists(path)?System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromAssemblyPath(path):null;};
Probe.Run(args);
static class Probe
{
 [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
 public static void Run(string[] args)
 {
  string root=Path.GetFullPath(args[0]),reference=Path.GetFullPath(args[1]);int width=args.Length>2?int.Parse(args[2]):796,height=args.Length>3?int.Parse(args[3]):404;
  string output=Path.Combine(root,"build/CSharpDocuments/WebsiteLayout-"+DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());Directory.CreateDirectory(output);
  var metrics=new Metrics(root);var entries=new List<object>();
  using var original=JsonDocument.Parse(File.ReadAllText(reference));
  foreach(bool dark in new[]{false,true})
  {
   var browser=new BrowserService(new RejectNetwork());browser.ResizeViewport(width,height);browser.NavigateDocument(WebsiteAssets.ReadText("Fixtures/WebsiteParity.md"),"WebsiteParity.md",dark);
   browser.Layout(new(width,height),metrics);
   for(int frame=1;frame<=8;frame++)browser.AdvanceTime(frame*20);
   var layout=browser.Layout(new(width,height),metrics);var doc=browser.Document!;string theme=dark?"dark":"light";
   var expected=original.RootElement.GetProperty("States").EnumerateArray().Single(state=>state.GetProperty("Theme").GetString()==theme);
   var comparison=new List<object>();
   foreach(var item in new[]{("Article",".markdown-body"),("Heading","h1"),("Code","pre.code-block-enhanced"),("Quote","blockquote"),("Table","table")})
   {
    var node=doc.QuerySelector(item.Item2)??throw new InvalidOperationException("Missing selector "+item.Item2+"; "+browser.RefreshDocument().Text);var box=layout.GetBoundingClientRect(node);var style=layout.GetComputedStyle(node)!;var e=expected.GetProperty(item.Item1);
    comparison.Add(new{Name=item.Item1,Actual=box,Reference=e,Delta=new{X=box.X-e.GetProperty("X").GetDouble(),Y=box.Y-e.GetProperty("Y").GetDouble(),Width=box.Width-e.GetProperty("Width").GetDouble(),Height=box.Height-e.GetProperty("Height").GetDouble()},Font=style.Font,style.LineHeight,Background=style.Background,Properties=style.Properties});
   }
   File.WriteAllText(Path.Combine(output,(dark?"Dark":"Light")+"Dom.html"),doc.DocumentElement!.OuterHtml);
   File.WriteAllText(Path.Combine(output,(dark?"Dark":"Light")+"Readback.json"),browser.ReadWebsiteDocumentProof());
   var boxes=layout.Boxes.Where(box=>box.Node is IElement).Select(box=>new{Tag=((IElement)box.Node).LocalName,Class=((IElement)box.Node).ClassName,box.ContentBox,box.BorderBox,box.MarginBox,box.PaddingBox,box.Style.Font,box.Style.LineHeight}).ToArray();
   File.WriteAllText(Path.Combine(output,(dark?"Dark":"Light")+"Boxes.json"),JsonSerializer.Serialize(boxes,new JsonSerializerOptions{WriteIndented=true}));
   entries.Add(new{Theme=theme,Comparison=comparison,layout.DocumentHeight,Diagnostics=layout.Diagnostics,GuestTested=false});
  }
  string result=Path.Combine(output,"Result.json");File.WriteAllText(result,JsonSerializer.Serialize(new{Schema="XfeWebsiteLayoutProbe1",HostOnly=true,NativeGuestTested=false,HostBrowserUsed=false,Viewport=new{Width=width,Height=height},ReferencePath=reference,FontFallback="Real pinned Cosmos TrueType advance/kerning: Lato Regular/Bold, Cascadia Mono, XFE Web CJK Regular/Bold when primary glyph is absent; final missing glyph uses existing fallback advance",States=entries},new JsonSerializerOptions{WriteIndented=true}));Console.WriteLine(result);
 }
 private sealed class RejectNetwork:IWebTransport{public WebResponse Get(Uri address,BrowserLimits limits)=>throw new InvalidOperationException("Layout probe cannot access network: "+address);}
 private sealed class Metrics:IBrowserTextMetrics
 {
  private readonly (TrueTypeFont Font,NativeWebFontMetrics Metrics) mono,sans,bold,cjk,cjkBold;
  public Metrics(string root){(TrueTypeFont,NativeWebFontMetrics) Load(string relative){byte[] bytes=File.ReadAllBytes(Path.Combine(root,relative));return(new(bytes),new(bytes));}mono=Load("assets/fonts/cascadia-mono-2407.24/CascadiaMono-Regular.ttf");sans=Load("assets/fonts/lato-2.015/Lato-Regular.ttf");bold=Load("assets/fonts/lato-2.015/Lato-Bold.ttf");cjk=Load("assets/fonts/noto-sans-sc-2.004/XfeWebCjk-Regular.ttf");cjkBold=Load("assets/fonts/noto-sans-sc-2.004/XfeWebCjk-Bold.ttf");}
  public double Measure(string text,BrowserFont style)
  {
   var face=style.Family.Contains("monospace",StringComparison.OrdinalIgnoreCase)||style.Family.Contains("Consolas",StringComparison.OrdinalIgnoreCase)?mono:style.Weight>=600?bold:sans;
   int height=(int)Math.Round(style.Size*64*face.Metrics.LineToEm);double value=0;char previous='\0';
   foreach(char ch in text){if(previous!='\0'&&face.Font.HasGlyph(previous)&&face.Font.HasGlyph(ch))value+=face.Font.GetKerning(previous,ch,height)/64d;var selected=face.Font.HasGlyph(ch)?face:style.Weight>=600?cjkBold:cjk;int selectedHeight=(int)Math.Round(style.Size*64*selected.Metrics.LineToEm);value+=selected.Font.HasGlyph(ch)?selected.Font.GetAdvance(ch,selectedHeight)/64d:style.Size;previous=ch;}return value;
  }
 }
}