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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using XFESystem.Desktop;

/// <summary>Offscreen host GDI+ rasterization of the actual pinned TTF. No browser or visible window.</summary>
internal sealed class WindowsTextCanvas : IDesktopWebCanvas, IDisposable
{
    private readonly PrivateFontCollection fonts = new();
    private readonly Bitmap surface;
    private readonly Graphics graphics;
    private readonly Stack<GraphicsState> clips = [];
    private readonly StringFormat format = (StringFormat)StringFormat.GenericTypographic.Clone();
    private readonly Dictionary<(int Size,int Weight),Font> fontCache = [];
    public WindowsTextCanvas(string fontPath,int width=800,int height=600)
    {
        fonts.AddFontFile(fontPath);
        FontSha256=Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(fontPath)));
        FontFamilyName=fonts.Families.Single().Name;
        surface=new Bitmap(width,height,PixelFormat.Format32bppArgb);
        graphics=Graphics.FromImage(surface);graphics.TextRenderingHint=TextRenderingHint.AntiAliasGridFit;
        graphics.SmoothingMode=SmoothingMode.AntiAlias;graphics.Clear(Color.Transparent);
        format.FormatFlags|=StringFormatFlags.MeasureTrailingSpaces;
    }
    public string FontSha256 {get;}
    public string FontFamilyName {get;}
    public long RasterCalls {get;private set;}
    public long RasterInkPixels {get;private set;}
    public long MeasureCalls {get;private set;}
    public int Presents {get;private set;}
    public int Width=>surface.Width;
    public int Height=>surface.Height;
    private Font Font(double size,int weight)
    {
        var key=((int)Math.Round(Math.Clamp(size,1,128)*64),weight>=600?700:400);
        if(!fontCache.TryGetValue(key,out var font)){
            font=new Font(fonts.Families.Single(),key.Item1/64f,key.Item2>=600?FontStyle.Bold:FontStyle.Regular,GraphicsUnit.Pixel);
            fontCache.Add(key,font);
        }
        return font;
    }
    public double MeasureWebText(string text,double fontSize,string fontFamily,int fontWeight)
    {
        MeasureCalls++;
        return text.Length==0?0:graphics.MeasureString(text,Font(fontSize,fontWeight),int.MaxValue,format).Width;
    }
    public (int Width,int Height,byte[] Alpha) RasterizeWebText(string text,double fontSize,string fontFamily,int fontWeight)
    {
        if(text.Length>4096)throw new ArgumentOutOfRangeException(nameof(text));
        var font=Font(fontSize,fontWeight);
        int width=Math.Max(1,(int)Math.Ceiling(MeasureWebText(text,fontSize,fontFamily,fontWeight)));
        int height=Math.Max(1,(int)Math.Ceiling(font.GetHeight(graphics)));
        using var bitmap=new Bitmap(width,height,PixelFormat.Format32bppArgb);
        using(var g=Graphics.FromImage(bitmap)){
            g.TextRenderingHint=TextRenderingHint.AntiAliasGridFit;g.Clear(Color.Transparent);
            g.DrawString(text,font,Brushes.White,0,0,format);
        }
        var locked=bitmap.LockBits(new Rectangle(0,0,width,height),ImageLockMode.ReadOnly,PixelFormat.Format32bppArgb);
        var alpha=new byte[checked(width*height)];
        try{
            var row=new int[width];
            for(int y=0;y<height;y++){
                Marshal.Copy(locked.Scan0+y*locked.Stride,row,0,width);
                for(int x=0;x<width;x++){alpha[y*width+x]=(byte)((uint)row[x]>>24);if(alpha[y*width+x]!=0)RasterInkPixels++;}
            }
        }finally{bitmap.UnlockBits(locked);}
        RasterCalls++;return(width,height,alpha);
    }
    public void Fill(uint rgb,int x,int y,int width,int height){using var brush=new SolidBrush(Color.FromArgb((int)(0xFF000000|rgb)));graphics.FillRectangle(brush,x,y,width,height);}
    public void Line(uint rgb,int x1,int y1,int x2,int y2){using var pen=new Pen(Color.FromArgb((int)(0xFF000000|rgb)));graphics.DrawLine(pen,x1,y1,x2,y2);}
    public void Text(string text,uint rgb,int x,int y)=>WebText(text,rgb,x,y,13,FontFamilyName,400);
    public void Present(){Presents++;}
    public void PushClip(DesktopBounds bounds){clips.Push(graphics.Save());graphics.SetClip(new Rectangle(bounds.X,bounds.Y,bounds.Width,bounds.Height),CombineMode.Intersect);}
    public void PopClip(){if(!clips.TryPop(out var state))throw new InvalidOperationException("Unbalanced probe clip");graphics.Restore(state);}
    public void WebText(string text,uint color,double x,double y,double fontSize,string fontFamily,int fontWeight,double opacity=1){
        using var brush=new SolidBrush(Color.FromArgb((int)Math.Clamp(opacity*255,0,255),(int)((color>>16)&255),(int)((color>>8)&255),(int)(color&255)));
        graphics.DrawString(text,Font(fontSize,fontWeight),brush,(float)x,(float)y,format);
    }
    public void SavePng(string path)=>surface.Save(path,ImageFormat.Png);
    public void Dispose(){foreach(var font in fontCache.Values)font.Dispose();format.Dispose();graphics.Dispose();surface.Dispose();fonts.Dispose();}
}