using AngleSharp.Dom;
using XFESystem.Desktop;
using XFESystem.Browser;
namespace XfePaintBaseline;
public static class BaselinePagePainter
{
public static void Paint(IDesktopCanvas canvas, DesktopBounds viewport, BrowserDisplayList list, Func<INode, BrowserCanvasSurface?>? surfaces = null)
{
var web = canvas as IDesktopWebCanvas;
var rounded = new BaselineClipCanvas(canvas);
var clips = new Stack<BrowserRect>();
var clip = new BrowserRect(viewport.X, viewport.Y, viewport.Width, viewport.Height);
web?.PushClip(viewport);
canvas.Fill(0xFFFFFF, viewport.X, viewport.Y, viewport.Width, viewport.Height);
try
{
foreach (var item in list.Commands)
{
if (item.Opacity <= 0 && item.Kind is not (BrowserDrawKind.PushClip or BrowserDrawKind.PopClip)) continue;
var r = item.Bounds.Translate(viewport.X - list.Viewport.ScrollX, viewport.Y - list.Viewport.ScrollY);
if (item.Kind == BrowserDrawKind.PushClip) { clips.Push(clip); clip = clip.Intersect(r); rounded.Push(item.RoundedClip,viewport.X-list.Viewport.ScrollX,viewport.Y-list.Viewport.ScrollY); web?.PushClip(ToBounds(clip)); continue; }
if (item.Kind == BrowserDrawKind.PopClip) { if (clips.TryPop(out var old)) { clip = old;rounded.Pop(); web?.PopClip(); } continue; }
var visible = r.Intersect(clip); if (visible.Width <= 0 || visible.Height <= 0) continue;
IDesktopCanvas target=rounded.Active&&canvas is IDesktopCoverageCanvas?rounded:canvas;
if(item.Decoration is not null) BaselineBoxPainter.Paint(target,item,r,visible);
else if (item.Kind == BrowserDrawKind.Canvas)
{ if (item.Node is not null && surfaces?.Invoke(item.Node) is { } surface) PaintCanvas(target, r, visible, surface, item.Opacity); }
else if (item.Kind == BrowserDrawKind.Text)
{
double leading=web is null?0:(r.Height-web.MeasureWebLineHeight(item.Font.Size,item.Font.Family,item.Font.Weight))/2;
if(web is not null&&rounded.Active&&canvas is IDesktopCoverageCanvas)rounded.WebText(web,item,r.Translate(0,leading));
else if (web is not null) web.WebText(item.Text, item.Color, r.X, r.Y+leading, item.Font.Size, item.Font.Family, item.Font.Weight, item.Opacity);
else if (visible == r) canvas.Text(item.Text, item.Color, (int)r.X, (int)r.Y);
}
else
{
var b = ToBounds(visible);
if (item.Opacity < 1 && target is IDesktopCoverageCanvas coverage)
{
int left = (int)Math.Floor(visible.X * canvas.ScalePercent / 100d), top = (int)Math.Floor(visible.Y * canvas.ScalePercent / 100d);
int width = Math.Max(1, (int)Math.Ceiling(visible.Width * canvas.ScalePercent / 100d)), height = Math.Max(1, (int)Math.Ceiling(visible.Height * canvas.ScalePercent / 100d));
var row = new byte[width]; Array.Fill(row, (byte)Math.Clamp(item.Opacity * 255, 0, 255));
for (int y = 0; y < height; y++) coverage.BlendCoverage(item.Color, left, top + y, width, 1, row);
}
else target.Fill(item.Color, b.X, b.Y, b.Width, b.Height);
}
}
}
catch { while (clips.TryPop(out _)) web?.PopClip(); web?.PopClip(); throw; }
while (clips.TryPop(out _)) web?.PopClip(); web?.PopClip();
}
private static void PaintCanvas(IDesktopCanvas canvas, BrowserRect bounds, BrowserRect visible, BrowserCanvasSurface surface, double opacity)
{
if (surface.Width <= 0 || surface.Height <= 0 || bounds.Width <= 0 || bounds.Height <= 0) return;
var pixels = surface.Pixels.Span;
int left = (int)Math.Floor(visible.Left), right = (int)Math.Ceiling(visible.Right), top = (int)Math.Floor(visible.Top), bottom = (int)Math.Ceiling(visible.Bottom);
for (int row = top; row < bottom; row++)
{
int sourceY = Math.Clamp((int)((row - bounds.Y) * surface.Height / bounds.Height), 0, surface.Height - 1);
for (int x = left; x < right;)
{
int sourceX = Math.Clamp((int)((x - bounds.X) * surface.Width / bounds.Width), 0, surface.Width - 1);
uint color = pixels[sourceY * surface.Width + sourceX]; int end = x + 1;
while (end < right && pixels[sourceY * surface.Width + Math.Clamp((int)((end - bounds.X) * surface.Width / bounds.Width), 0, surface.Width - 1)] == color) end++;
int alpha = (int)((color >> 24) * opacity);
if (alpha >= 255) canvas.Fill(color & 0xFFFFFF, x, row, end - x, 1);
else if (alpha > 0 && canvas is IDesktopCoverageCanvas coverage)
{
int nx = (int)Math.Round(x * canvas.ScalePercent / 100d), ny = (int)Math.Round(row * canvas.ScalePercent / 100d);
int nw = Math.Max(1,(int)Math.Round(end * canvas.ScalePercent / 100d)-nx), nh = Math.Max(1,(int)Math.Round((row+1) * canvas.ScalePercent / 100d)-ny);
var mask = new byte[nw]; Array.Fill(mask,(byte)alpha);
for (int sy = 0; sy < nh; sy++) coverage.BlendCoverage(color & 0xFFFFFF, nx, ny + sy, nw,1,mask);
}
x = end;
}
}
}
private static DesktopBounds ToBounds(BrowserRect r) => new((int)Math.Floor(r.X), (int)Math.Floor(r.Y), Math.Max(0, (int)Math.Ceiling(r.Right) - (int)Math.Floor(r.X)), Math.Max(0, (int)Math.Ceiling(r.Bottom) - (int)Math.Floor(r.Y)));
}
using AngleSharp.Dom;
using XFESystem.Desktop;
using XFESystem.Browser;
namespace XfePaintBaseline;
public static class BaselinePagePainter
{
public static void Paint(IDesktopCanvas canvas, DesktopBounds viewport, BrowserDisplayList list, Func<INode, BrowserCanvasSurface?>? surfaces = null)
{
var web = canvas as IDesktopWebCanvas;
var rounded = new BaselineClipCanvas(canvas);
var clips = new Stack<BrowserRect>();
var clip = new BrowserRect(viewport.X, viewport.Y, viewport.Width, viewport.Height);
web?.PushClip(viewport);
canvas.Fill(0xFFFFFF, viewport.X, viewport.Y, viewport.Width, viewport.Height);
try
{
foreach (var item in list.Commands)
{
if (item.Opacity <= 0 && item.Kind is not (BrowserDrawKind.PushClip or BrowserDrawKind.PopClip)) continue;
var r = item.Bounds.Translate(viewport.X - list.Viewport.ScrollX, viewport.Y - list.Viewport.ScrollY);
if (item.Kind == BrowserDrawKind.PushClip) { clips.Push(clip); clip = clip.Intersect(r); rounded.Push(item.RoundedClip,viewport.X-list.Viewport.ScrollX,viewport.Y-list.Viewport.ScrollY); web?.PushClip(ToBounds(clip)); continue; }
if (item.Kind == BrowserDrawKind.PopClip) { if (clips.TryPop(out var old)) { clip = old;rounded.Pop(); web?.PopClip(); } continue; }
var visible = r.Intersect(clip); if (visible.Width <= 0 || visible.Height <= 0) continue;
IDesktopCanvas target=rounded.Active&&canvas is IDesktopCoverageCanvas?rounded:canvas;
if(item.Decoration is not null) BaselineBoxPainter.Paint(target,item,r,visible);
else if (item.Kind == BrowserDrawKind.Canvas)
{ if (item.Node is not null && surfaces?.Invoke(item.Node) is { } surface) PaintCanvas(target, r, visible, surface, item.Opacity); }
else if (item.Kind == BrowserDrawKind.Text)
{
double leading=web is null?0:(r.Height-web.MeasureWebLineHeight(item.Font.Size,item.Font.Family,item.Font.Weight))/2;
if(web is not null&&rounded.Active&&canvas is IDesktopCoverageCanvas)rounded.WebText(web,item,r.Translate(0,leading));
else if (web is not null) web.WebText(item.Text, item.Color, r.X, r.Y+leading, item.Font.Size, item.Font.Family, item.Font.Weight, item.Opacity);
else if (visible == r) canvas.Text(item.Text, item.Color, (int)r.X, (int)r.Y);
}
else
{
var b = ToBounds(visible);
if (item.Opacity < 1 && target is IDesktopCoverageCanvas coverage)
{
int left = (int)Math.Floor(visible.X * canvas.ScalePercent / 100d), top = (int)Math.Floor(visible.Y * canvas.ScalePercent / 100d);
int width = Math.Max(1, (int)Math.Ceiling(visible.Width * canvas.ScalePercent / 100d)), height = Math.Max(1, (int)Math.Ceiling(visible.Height * canvas.ScalePercent / 100d));
var row = new byte[width]; Array.Fill(row, (byte)Math.Clamp(item.Opacity * 255, 0, 255));
for (int y = 0; y < height; y++) coverage.BlendCoverage(item.Color, left, top + y, width, 1, row);
}
else target.Fill(item.Color, b.X, b.Y, b.Width, b.Height);
}
}
}
catch { while (clips.TryPop(out _)) web?.PopClip(); web?.PopClip(); throw; }
while (clips.TryPop(out _)) web?.PopClip(); web?.PopClip();
}
private static void PaintCanvas(IDesktopCanvas canvas, BrowserRect bounds, BrowserRect visible, BrowserCanvasSurface surface, double opacity)
{
if (surface.Width <= 0 || surface.Height <= 0 || bounds.Width <= 0 || bounds.Height <= 0) return;
var pixels = surface.Pixels.Span;
int left = (int)Math.Floor(visible.Left), right = (int)Math.Ceiling(visible.Right), top = (int)Math.Floor(visible.Top), bottom = (int)Math.Ceiling(visible.Bottom);
for (int row = top; row < bottom; row++)
{
int sourceY = Math.Clamp((int)((row - bounds.Y) * surface.Height / bounds.Height), 0, surface.Height - 1);
for (int x = left; x < right;)
{
int sourceX = Math.Clamp((int)((x - bounds.X) * surface.Width / bounds.Width), 0, surface.Width - 1);
uint color = pixels[sourceY * surface.Width + sourceX]; int end = x + 1;
while (end < right && pixels[sourceY * surface.Width + Math.Clamp((int)((end - bounds.X) * surface.Width / bounds.Width), 0, surface.Width - 1)] == color) end++;
int alpha = (int)((color >> 24) * opacity);
if (alpha >= 255) canvas.Fill(color & 0xFFFFFF, x, row, end - x, 1);
else if (alpha > 0 && canvas is IDesktopCoverageCanvas coverage)
{
int nx = (int)Math.Round(x * canvas.ScalePercent / 100d), ny = (int)Math.Round(row * canvas.ScalePercent / 100d);
int nw = Math.Max(1,(int)Math.Round(end * canvas.ScalePercent / 100d)-nx), nh = Math.Max(1,(int)Math.Round((row+1) * canvas.ScalePercent / 100d)-ny);
var mask = new byte[nw]; Array.Fill(mask,(byte)alpha);
for (int sy = 0; sy < nh; sy++) coverage.BlendCoverage(color & 0xFFFFFF, nx, ny + sy, nw,1,mask);
}
x = end;
}
}
}
private static DesktopBounds ToBounds(BrowserRect r) => new((int)Math.Floor(r.X), (int)Math.Floor(r.Y), Math.Max(0, (int)Math.Ceiling(r.Right) - (int)Math.Floor(r.X)), Math.Max(0, (int)Math.Ceiling(r.Bottom) - (int)Math.Floor(r.Y)));
}