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); } } }