using XFESystem.Desktop;
namespace XFESystem.Browser;
/// <summary>Coverage clipping for rounded overflow, independent of a platform graphics API.</summary>
internal sealed class BrowserClipRasterCanvas(IDesktopCanvas canvas) : IDesktopCoverageCanvas, IDesktopRenderYieldCanvas
{
private readonly List<BrowserRoundedClip> shapes=[];
private readonly Stack<int> checkpoints=[];
private byte[] rowBuffer=[];
public int Width=>canvas.Width;
public int Height=>canvas.Height;
public int NativeWidth=>canvas.NativeWidth;
public int NativeHeight=>canvas.NativeHeight;
public int ScalePercent=>canvas.ScalePercent;
public bool Active=>shapes.Count>0;
public bool RenderYieldRequested => canvas is IDesktopRenderYieldCanvas yielding && yielding.RenderYieldRequested;
public void Push(BrowserRoundedClip? shape,double dx,double dy)
{
checkpoints.Push(shapes.Count);
if(shape is null)return;
double scale=ScalePercent/100d;var r=shape.Bounds.Translate(dx,dy);var radius=shape.Radius;
shapes.Add(new(new(r.X*scale,r.Y*scale,r.Width*scale,r.Height*scale),new(radius.Top*scale,radius.Right*scale,radius.Bottom*scale,radius.Left*scale)));
}
public void Pop(){if(checkpoints.TryPop(out int count)&&count<shapes.Count)shapes.RemoveRange(count,shapes.Count-count);}
public void BlendCoverage(uint rgb,int x,int y,int width,int height,ReadOnlySpan<byte> source)
{
if(canvas is not IDesktopCoverageCanvas coverage)return;
if(!Active){coverage.BlendCoverage(rgb,x,y,width,height,source);return;}
if(width<=0||height<=0||(long)width*height!=source.Length)throw new ArgumentException("Coverage dimensions do not match.");
// Clip before allocating. A page may have enormous offscreen geometry;
// only pixels inside the actual framebuffer need mask work.
int left=Math.Max(0,x),right=(int)Math.Min((long)NativeWidth,(long)x+width),top=Math.Max(0,y),bottom=(int)Math.Min((long)NativeHeight,(long)y+height);
if(right<=left||bottom<=top)return;
int rowWidth=right-left;
if (FullyCovered(left + .5, top + .5, right - .5, bottom - .5))
{ coverage.BlendCoverage(rgb,x,y,width,height,source); return; }
if(rowBuffer.Length<rowWidth)rowBuffer=new byte[rowWidth];
var row=rowBuffer;
for(int py=top;py<bottom;py++)
{
if(RenderYieldRequested)throw new DesktopRenderYieldException();
if (FullyCovered(left + .5, py + .5, right - .5, py + .5))
{ coverage.BlendCoverage(rgb,left,py,rowWidth,1,source.Slice((py-y)*width+left-x,rowWidth)); continue; }
bool any=false;
for(int px=left;px<right;px++)
{
double alpha=source[(py-y)*width+px-x];
foreach(var shape in shapes){alpha*=BrowserBoxRasterPainter.Coverage(shape.Bounds,shape.Radius,px+.5,py+.5);if(alpha==0)break;}
row[px-left]=(byte)Math.Clamp(Math.Round(alpha),0,255);any|=row[px-left]>0;
}
if(any)coverage.BlendCoverage(rgb,left,py,rowWidth,1,row.AsSpan(0,rowWidth));
}
}
private bool FullyCovered(double left,double top,double right,double bottom)
{
foreach(var shape in shapes)
if(BrowserBoxRasterPainter.Coverage(shape.Bounds,shape.Radius,left,top)<1 ||
BrowserBoxRasterPainter.Coverage(shape.Bounds,shape.Radius,right,top)<1 ||
BrowserBoxRasterPainter.Coverage(shape.Bounds,shape.Radius,left,bottom)<1 ||
BrowserBoxRasterPainter.Coverage(shape.Bounds,shape.Radius,right,bottom)<1) return false;
return true;
}
public void Fill(uint rgb,int x,int y,int width,int height)
{
if(!Active){canvas.Fill(rgb,x,y,width,height);return;}
double scale=ScalePercent/100d;int left=(int)Math.Round(x*scale),top=(int)Math.Round(y*scale),right=(int)Math.Round((x+(double)width)*scale),bottom=(int)Math.Round((y+(double)height)*scale);
left=Math.Max(0,left);top=Math.Max(0,top);right=Math.Min(NativeWidth,right);bottom=Math.Min(NativeHeight,bottom);
if(right<=left||bottom<=top)return;
var row=new byte[right-left];Array.Fill(row,(byte)255);
for(int py=top;py<bottom;py++)BlendCoverage(rgb,left,py,row.Length,1,row);
}
public void WebText(IDesktopWebCanvas web,BrowserDrawCommand command,BrowserRect bounds)
{
double scale=ScalePercent/100d;
var mask=web.RasterizeWebText(command.Text,command.Font.Size*scale,command.Font.Family,command.Font.Weight);
int left=(int)Math.Round(bounds.X*scale),top=(int)Math.Round(bounds.Y*scale);
if(command.Opacity>=1)BlendCoverage(command.Color,left,top,mask.Width,mask.Height,mask.Alpha);
else
{ for(int i=0;i<mask.Alpha.Length;i++)mask.Alpha[i]=(byte)Math.Clamp(Math.Round(mask.Alpha[i]*command.Opacity),0,255);BlendCoverage(command.Color,left,top,mask.Width,mask.Height,mask.Alpha); }
}
public void Line(uint rgb,int x1,int y1,int x2,int y2)=>canvas.Line(rgb,x1,y1,x2,y2);
public void Text(string text,uint rgb,int x,int y)=>canvas.Text(text,rgb,x,y);
public void Present()=>canvas.Present();
}
using XFESystem.Desktop;
namespace XFESystem.Browser;
/// <summary>Coverage clipping for rounded overflow, independent of a platform graphics API.</summary>
internal sealed class BrowserClipRasterCanvas(IDesktopCanvas canvas) : IDesktopCoverageCanvas, IDesktopRenderYieldCanvas
{
private readonly List<BrowserRoundedClip> shapes=[];
private readonly Stack<int> checkpoints=[];
private byte[] rowBuffer=[];
public int Width=>canvas.Width;
public int Height=>canvas.Height;
public int NativeWidth=>canvas.NativeWidth;
public int NativeHeight=>canvas.NativeHeight;
public int ScalePercent=>canvas.ScalePercent;
public bool Active=>shapes.Count>0;
public bool RenderYieldRequested => canvas is IDesktopRenderYieldCanvas yielding && yielding.RenderYieldRequested;
public void Push(BrowserRoundedClip? shape,double dx,double dy)
{
checkpoints.Push(shapes.Count);
if(shape is null)return;
double scale=ScalePercent/100d;var r=shape.Bounds.Translate(dx,dy);var radius=shape.Radius;
shapes.Add(new(new(r.X*scale,r.Y*scale,r.Width*scale,r.Height*scale),new(radius.Top*scale,radius.Right*scale,radius.Bottom*scale,radius.Left*scale)));
}
public void Pop(){if(checkpoints.TryPop(out int count)&&count<shapes.Count)shapes.RemoveRange(count,shapes.Count-count);}
public void BlendCoverage(uint rgb,int x,int y,int width,int height,ReadOnlySpan<byte> source)
{
if(canvas is not IDesktopCoverageCanvas coverage)return;
if(!Active){coverage.BlendCoverage(rgb,x,y,width,height,source);return;}
if(width<=0||height<=0||(long)width*height!=source.Length)throw new ArgumentException("Coverage dimensions do not match.");
// Clip before allocating. A page may have enormous offscreen geometry;
// only pixels inside the actual framebuffer need mask work.
int left=Math.Max(0,x),right=(int)Math.Min((long)NativeWidth,(long)x+width),top=Math.Max(0,y),bottom=(int)Math.Min((long)NativeHeight,(long)y+height);
if(right<=left||bottom<=top)return;
int rowWidth=right-left;
if (FullyCovered(left + .5, top + .5, right - .5, bottom - .5))
{ coverage.BlendCoverage(rgb,x,y,width,height,source); return; }
if(rowBuffer.Length<rowWidth)rowBuffer=new byte[rowWidth];
var row=rowBuffer;
for(int py=top;py<bottom;py++)
{
if(RenderYieldRequested)throw new DesktopRenderYieldException();
if (FullyCovered(left + .5, py + .5, right - .5, py + .5))
{ coverage.BlendCoverage(rgb,left,py,rowWidth,1,source.Slice((py-y)*width+left-x,rowWidth)); continue; }
bool any=false;
for(int px=left;px<right;px++)
{
double alpha=source[(py-y)*width+px-x];
foreach(var shape in shapes){alpha*=BrowserBoxRasterPainter.Coverage(shape.Bounds,shape.Radius,px+.5,py+.5);if(alpha==0)break;}
row[px-left]=(byte)Math.Clamp(Math.Round(alpha),0,255);any|=row[px-left]>0;
}
if(any)coverage.BlendCoverage(rgb,left,py,rowWidth,1,row.AsSpan(0,rowWidth));
}
}
private bool FullyCovered(double left,double top,double right,double bottom)
{
foreach(var shape in shapes)
if(BrowserBoxRasterPainter.Coverage(shape.Bounds,shape.Radius,left,top)<1 ||
BrowserBoxRasterPainter.Coverage(shape.Bounds,shape.Radius,right,top)<1 ||
BrowserBoxRasterPainter.Coverage(shape.Bounds,shape.Radius,left,bottom)<1 ||
BrowserBoxRasterPainter.Coverage(shape.Bounds,shape.Radius,right,bottom)<1) return false;
return true;
}
public void Fill(uint rgb,int x,int y,int width,int height)
{
if(!Active){canvas.Fill(rgb,x,y,width,height);return;}
double scale=ScalePercent/100d;int left=(int)Math.Round(x*scale),top=(int)Math.Round(y*scale),right=(int)Math.Round((x+(double)width)*scale),bottom=(int)Math.Round((y+(double)height)*scale);
left=Math.Max(0,left);top=Math.Max(0,top);right=Math.Min(NativeWidth,right);bottom=Math.Min(NativeHeight,bottom);
if(right<=left||bottom<=top)return;
var row=new byte[right-left];Array.Fill(row,(byte)255);
for(int py=top;py<bottom;py++)BlendCoverage(rgb,left,py,row.Length,1,row);
}
public void WebText(IDesktopWebCanvas web,BrowserDrawCommand command,BrowserRect bounds)
{
double scale=ScalePercent/100d;
var mask=web.RasterizeWebText(command.Text,command.Font.Size*scale,command.Font.Family,command.Font.Weight);
int left=(int)Math.Round(bounds.X*scale),top=(int)Math.Round(bounds.Y*scale);
if(command.Opacity>=1)BlendCoverage(command.Color,left,top,mask.Width,mask.Height,mask.Alpha);
else
{ for(int i=0;i<mask.Alpha.Length;i++)mask.Alpha[i]=(byte)Math.Clamp(Math.Round(mask.Alpha[i]*command.Opacity),0,255);BlendCoverage(command.Color,left,top,mask.Width,mask.Height,mask.Alpha); }
}
public void Line(uint rgb,int x1,int y1,int x2,int y2)=>canvas.Line(rgb,x1,y1,x2,y2);
public void Text(string text,uint rgb,int x,int y)=>canvas.Text(text,rgb,x,y);
public void Present()=>canvas.Present();
}