using System.Drawing;
using DebenuPDFLibraryDLL1013;
using EnvelopeRenderer.Cli.Render;
#pragma warning disable CA1416
namespace EnvelopeRenderer.Cli.Tests;
/// A <text> element with fill is a filled rectangle (cover-up) that its own text,
/// or any element with a higher render order, draws over.
public class FilledRectangleTests
{
private static string WriteTemplate(string xml)
{
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xml");
File.WriteAllText(path, xml);
return path;
}
private static string DllPath() => Path.Combine(
AppContext.BaseDirectory,
Environment.Is64BitProcess ? "DebenuPDFLibrary64DLL1013.dll" : "DebenuPDFLibraryDLL1013.dll");
[Fact]
public void Parse_FillWithBoxAndNoText_IsAPureRectangle()
{
var path = WriteTemplate("""
""");
var result = TemplateXmlParser.Parse(path);
Assert.True(result.Succeeded, string.Join("; ", result.Errors));
var element = Assert.Single(result.Document!.Elements);
Assert.True(element.HasFill);
Assert.Equal(new PdfColor(255, 255, 255), element.FillColor);
Assert.Equal(string.Empty, element.Runs[0].Literal);
}
[Theory]
[InlineData("""Hi""", "'width' and 'height'")]
[InlineData("""Hi""", "'fill'")]
[InlineData("""Hi""", "rotated")]
public void Parse_InvalidFill_Fails(string textXml, string expected)
{
var result = TemplateXmlParser.Parse(WriteTemplate(
$"""{textXml}"""));
Assert.False(result.Succeeded);
Assert.Contains(result.Errors, e => e.Contains(expected));
}
[Fact]
public void Render_FillElement_ProducesDrawWithFillColorAndBox_InRenderOrder()
{
var elements = new[]
{
new TemplateElement(10, 20, "Arial", 12, new[] { new TemplateTextRun(string.Empty, null) },
Width: 100, Height: 40, FillColor: new PdfColor(255, 255, 255)),
TemplateElement.Static(12, 30, "Arial", 12, "over") with { RenderOrder = 1 },
};
var template = new TemplateDocument(297, 684, elements, Array.Empty());
var draws = new List>();
var renderer = new CapturingRenderer(draws);
var result = RenderEngine.Render(
template, new[] { "A" },
new List> { new Dictionary { ["A"] = "1" } },
renderer, "out.pdf");
Assert.True(result.Succeeded, string.Join("; ", result.Errors));
Assert.Equal(new PdfColor(255, 255, 255), draws[0][0].FillColor);
Assert.Equal(100, draws[0][0].Width);
Assert.Equal("over", draws[0][1].Text);
Assert.Null(draws[0][1].FillColor);
}
private sealed class CapturingRenderer : IPdfRenderer
{
private readonly List> _draws;
public CapturingRenderer(List> draws) => _draws = draws;
public bool AddPage(double w, double h, IReadOnlyList draws, PageBackground? background, out string? error)
{
_draws.Add(draws);
error = null;
return true;
}
public bool Save(string outputPath, out string? error)
{
error = null;
return true;
}
public void Dispose() { }
}
[Fact]
public void RealRender_FilledRectangleCoversBackground_AndTextDrawsOverIt()
{
var key = DebenuLicenseKey.Resolve();
if (key is null)
{
Console.WriteLine("SKIPPED: no Debenu license key available locally.");
return;
}
var dir = Path.Combine(Path.GetTempPath(), $"rect-{Guid.NewGuid():N}");
Directory.CreateDirectory(dir);
try
{
var imagePath = Path.Combine(dir, "red.png");
using (var bitmap = new Bitmap(40, 40))
{
using var g = Graphics.FromImage(bitmap);
g.Clear(Color.Red);
bitmap.Save(imagePath, System.Drawing.Imaging.ImageFormat.Png);
}
// 200x200 page, red background; a white 100x100 rectangle at (50,50) with black text in it.
var rectangle = new TemplateElement(50, 50, "Arial", 40,
new[] { new TemplateTextRun("MMMM", null) },
Width: 100, Height: 100, FillColor: new PdfColor(255, 255, 255));
var template = new TemplateDocument(200, 200, new[] { rectangle },
Array.Empty(),
new TemplateBackground("red.png", null, 1, null, dir));
var output = Path.Combine(dir, "out.pdf");
Assert.True(DebenuPdfRenderer.TryCreate(DllPath(), key, out var renderer, out var error), error);
using (renderer)
{
var result = RenderEngine.Render(
template, new[] { "A" },
new List> { new Dictionary { ["A"] = "1" } },
renderer!, output);
Assert.True(result.Succeeded, string.Join("; ", result.Errors));
}
var pdf = new PDFLibrary(DllPath());
pdf.UnlockKey(key);
Assert.NotEqual(0, pdf.LoadFromFile(output, ""));
var png = Path.Combine(dir, "page.png");
Assert.NotEqual(0, pdf.RenderPageToFile(72, 1, 0, png));
pdf.ReleaseLibrary();
using var rendered = new Bitmap(png);
var corner = rendered.GetPixel(5, 5);
Assert.True(corner.R > 200 && corner.G < 60, $"outside the rectangle the red background stays: {corner}");
// Inside the rectangle (PDF y=50..150 is image rows 50..150): white except where text draws.
int white = 0, dark = 0, red = 0;
for (var x = 55; x < 145; x++)
{
for (var y = 55; y < 145; y++)
{
var c = rendered.GetPixel(x, y);
if (c.R > 230 && c.G > 230 && c.B > 230) { white++; }
else if (c.R < 90 && c.G < 90 && c.B < 90) { dark++; }
else if (c.R > 200 && c.G < 60 && c.B < 60) { red++; }
}
}
Assert.Equal(0, red);
Assert.True(white > 3000, $"the rectangle should be mostly white: {white}");
Assert.True(dark > 100, $"text should draw over the rectangle: {dark}");
}
finally
{
Directory.Delete(dir, recursive: true);
}
}
}