Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f927ec402 | |||
| 9dc3e2b533 | |||
| 3369da42ed | |||
| c5cb588857 | |||
| 08126b2fa4 | |||
| 5e9ff7ec1e | |||
| 9c05c182b8 | |||
| dee97b22e4 | |||
| 1d1d969b5a |
@@ -134,5 +134,37 @@ namespace QuestPDF.Examples
|
||||
.Height(150);
|
||||
});
|
||||
}
|
||||
|
||||
//[ShowResult]
|
||||
[ImageSize(300, 200)]
|
||||
public void Debug(IContainer container)
|
||||
{
|
||||
container
|
||||
.Padding(25)
|
||||
.Debug()
|
||||
.Padding(-5)
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Padding(5).Extend().Placeholder();
|
||||
row.RelativeColumn().Padding(5).Extend().Placeholder();
|
||||
});
|
||||
}
|
||||
|
||||
[ShowResult]
|
||||
[ImageSize(300, 200)]
|
||||
public void ElementEnd(IContainer container)
|
||||
{
|
||||
var text = "";
|
||||
|
||||
container
|
||||
.Padding(10)
|
||||
.Element(x =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
x.Height(10).Width(50).Background("#DDD");
|
||||
else
|
||||
x.Text(text);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,10 +62,17 @@ namespace QuestPDF.Examples.Engine
|
||||
methodInfo.Invoke(this, new object[] {container});
|
||||
|
||||
Func<int, string> fileNameSchema = i => $"{fileName.ToLower()}-${i}.png";
|
||||
|
||||
var document = new SimpleDocument(container, size);
|
||||
document.GenerateImages(fileNameSchema);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var document = new SimpleDocument(container, size);
|
||||
document.GenerateImages(fileNameSchema);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception($"Cannot perform test ${fileName}", e);
|
||||
}
|
||||
|
||||
if (showResult)
|
||||
Process.Start("explorer", fileNameSchema(0));
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace QuestPDF.Examples.Engine
|
||||
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container.Element(Container.Child);
|
||||
container.Background("#FFF").Element(Container.Child);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\QuestPDF.InvoiceSample\QuestPDF.InvoiceSample.csproj" />
|
||||
<ProjectReference Include="..\QuestPDF\QuestPDF.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ namespace QuestPDF.ReportSample
|
||||
HeaderFields = HeaderFields(),
|
||||
|
||||
LogoData = Helpers.GetImage("logo.png"),
|
||||
Sections = Enumerable.Range(0, 10).Select(x => GenerateSection()).ToList(),
|
||||
Photos = Enumerable.Range(0, 10).Select(x => GetReportPhotos()).ToList()
|
||||
Sections = Enumerable.Range(0, 8).Select(x => GenerateSection()).ToList(),
|
||||
Photos = Enumerable.Range(0, 8).Select(x => GetReportPhotos()).ToList()
|
||||
};
|
||||
|
||||
List<ReportHeaderField> HeaderFields()
|
||||
|
||||
@@ -38,10 +38,10 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
frame.Text(text.Text, Typography.Normal);
|
||||
|
||||
if (part is ReportSectionMap map)
|
||||
frame.Element(container => MapElement(container, map));
|
||||
frame.Element(x => MapElement(x, map));
|
||||
|
||||
if (part is ReportSectionPhotos photos)
|
||||
frame.Element(container => PhotosElement(container, photos));
|
||||
frame.Element(x => PhotosElement(x, photos));
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -56,7 +56,7 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
return;
|
||||
}
|
||||
|
||||
container.Stack(stack =>
|
||||
container.PageableStack(stack =>
|
||||
{
|
||||
stack.Spacing(5);
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace QuestPDF.ReportSample.Layouts
|
||||
|
||||
var rowCount = (int) Math.Ceiling(model.Photos.Count / 3f);
|
||||
|
||||
container.Padding(-2).Stack(stack =>
|
||||
container.Debug().Padding(-2).PageableStack(stack =>
|
||||
{
|
||||
foreach (var rowId in Enumerable.Range(0, rowCount))
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -18,8 +18,7 @@ namespace QuestPDF.ReportSample
|
||||
var report = new StandardReport(reportModel);
|
||||
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"test_result.pdf");
|
||||
using var stream = new FileStream(path, FileMode.Create);
|
||||
report.GeneratePdf(stream);
|
||||
report.GeneratePdf(path);
|
||||
|
||||
Process.Start("explorer.exe", path);
|
||||
}
|
||||
@@ -28,7 +27,7 @@ namespace QuestPDF.ReportSample
|
||||
public void PerformanceBenchmark()
|
||||
{
|
||||
// test size
|
||||
const int testSize = 1000;
|
||||
const int testSize = 250;
|
||||
const decimal performanceTarget = 5; // documents per second
|
||||
|
||||
// create report models
|
||||
|
||||
@@ -5,8 +5,8 @@ namespace QuestPDF.ReportSample
|
||||
{
|
||||
public static class Typography
|
||||
{
|
||||
public static TextStyle Title => TextStyle.Default.FontType("Helvetica").Color("#000000").Size(20).Bold();
|
||||
public static TextStyle Headline => TextStyle.Default.FontType("Helvetica").Color("#047AED").Size(14);
|
||||
public static TextStyle Title => TextStyle.Default.FontType("Helvetica").Color("#000000").Size(22).SemiBold();
|
||||
public static TextStyle Headline => TextStyle.Default.FontType("Helvetica").Color("#047AED").Size(14).SemiBold();
|
||||
public static TextStyle Normal => TextStyle.Default.FontType("Helvetica").Color("#000000").Size(10).LineHeight(1.25f).AlignLeft();
|
||||
}
|
||||
}
|
||||
@@ -14,20 +14,7 @@ namespace QuestPDF.UnitTests
|
||||
|
||||
[Test]
|
||||
public void Draw_ShouldHandleNullChild() => new Alignment().DrawWithoutChild();
|
||||
|
||||
[Test]
|
||||
public void Measure_ShouldReturnWrap_WhenChildReturnsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Alignment
|
||||
{
|
||||
Child = x.CreateChild("child")
|
||||
})
|
||||
.MeasureElement(new Size(1000, 500))
|
||||
.ExpectChildMeasure("child", expectedInput: new Size(1000, 500), returns: new Wrap())
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void Draw_HorizontalCenter_VerticalCenter()
|
||||
{
|
||||
@@ -37,12 +24,12 @@ namespace QuestPDF.UnitTests
|
||||
Horizontal = HorizontalAlignment.Center,
|
||||
Vertical = VerticalAlignment.Middle,
|
||||
|
||||
Child = x.CreateChild("child")
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.DrawElement(new Size(1000, 500))
|
||||
.ExpectChildMeasure("child", expectedInput: new Size(1000, 500), returns: new PartialRender(new Size(400, 200)))
|
||||
.ExpectChildMeasure(expectedInput: new Size(1000, 500), returns: new PartialRender(new Size(400, 200)))
|
||||
.ExpectCanvasTranslate(new Position(300, 150))
|
||||
.ExpectChildDraw("child", new Size(400, 200))
|
||||
.ExpectChildDraw(new Size(400, 200))
|
||||
.ExpectCanvasTranslate(new Position(-300, -150))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
@@ -56,12 +43,12 @@ namespace QuestPDF.UnitTests
|
||||
Horizontal = HorizontalAlignment.Left,
|
||||
Vertical = VerticalAlignment.Middle,
|
||||
|
||||
Child = x.CreateChild("child")
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("child", expectedInput: new Size(400, 300), returns: new FullRender(new Size(100, 50)))
|
||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: new FullRender(new Size(100, 50)))
|
||||
.ExpectCanvasTranslate(new Position(0, 125))
|
||||
.ExpectChildDraw("child", new Size(100, 50))
|
||||
.ExpectChildDraw(new Size(100, 50))
|
||||
.ExpectCanvasTranslate(new Position(0, -125))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
@@ -75,12 +62,12 @@ namespace QuestPDF.UnitTests
|
||||
Horizontal = HorizontalAlignment.Center,
|
||||
Vertical = VerticalAlignment.Bottom,
|
||||
|
||||
Child = x.CreateChild("child")
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("child", expectedInput: new Size(400, 300), returns: new FullRender(new Size(100, 50)))
|
||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: new FullRender(new Size(100, 50)))
|
||||
.ExpectCanvasTranslate(new Position(150, 250))
|
||||
.ExpectChildDraw("child", new Size(100, 50))
|
||||
.ExpectChildDraw(new Size(100, 50))
|
||||
.ExpectCanvasTranslate(new Position(-150, -250))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
@@ -94,12 +81,12 @@ namespace QuestPDF.UnitTests
|
||||
Horizontal = HorizontalAlignment.Right,
|
||||
Vertical = VerticalAlignment.Top,
|
||||
|
||||
Child = x.CreateChild("child")
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("child", expectedInput: new Size(400, 300), returns: new FullRender(new Size(100, 50)))
|
||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: new FullRender(new Size(100, 50)))
|
||||
.ExpectCanvasTranslate(new Position(300, 0))
|
||||
.ExpectChildDraw("child", new Size(100, 50))
|
||||
.ExpectChildDraw(new Size(100, 50))
|
||||
.ExpectCanvasTranslate(new Position(-300, 0))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
@@ -15,5 +15,168 @@ namespace QuestPDF.UnitTests
|
||||
|
||||
[Test]
|
||||
public void Draw_ShouldHandleNullChild() => new AspectRatio().DrawWithoutChild();
|
||||
|
||||
[Test]
|
||||
public void Measure_FitWidth_EnoughSpace_FullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitArea,
|
||||
Ratio = 2f
|
||||
})
|
||||
.MeasureElement(new Size(400, 201))
|
||||
.ExpectChildMeasure(new Size(400, 200), new FullRender(100, 50))
|
||||
.CheckMeasureResult(new FullRender(400, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_FitWidth_EnoughSpace_PartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitArea,
|
||||
Ratio = 2f
|
||||
})
|
||||
.MeasureElement(new Size(400, 201))
|
||||
.ExpectChildMeasure(new Size(400, 200), new PartialRender(100, 50))
|
||||
.CheckMeasureResult(new PartialRender(400, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_FitWidth_EnoughSpace_Wrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitArea,
|
||||
Ratio = 2f
|
||||
})
|
||||
.MeasureElement(new Size(400, 201))
|
||||
.ExpectChildMeasure(new Size(400, 200), new Wrap())
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_FitWidth_EnoughSpace()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitWidth,
|
||||
Ratio = 2f
|
||||
})
|
||||
.MeasureElement(new Size(400, 201))
|
||||
.ExpectChildMeasure(new Size(400, 200), new FullRender(100, 50))
|
||||
.CheckMeasureResult(new FullRender(400, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_FitWidth_NotEnoughSpace()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitWidth,
|
||||
Ratio = 2f
|
||||
})
|
||||
.MeasureElement(new Size(400, 199))
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_FitHeight_EnoughSpace()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitHeight,
|
||||
Ratio = 2f
|
||||
})
|
||||
.MeasureElement(new Size(401, 200))
|
||||
.ExpectChildMeasure(new Size(400, 200), new FullRender(100, 50))
|
||||
.CheckMeasureResult(new FullRender(400, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_FitHeight_NotEnoughSpace()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitHeight,
|
||||
Ratio = 2f
|
||||
})
|
||||
.MeasureElement(new Size(399, 200))
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_FitArea_ToWidth()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitArea,
|
||||
Ratio = 2f
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(new Size(400, 200), new FullRender(100, 50))
|
||||
.CheckMeasureResult(new FullRender(400, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_FitArea_ToHeight()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitArea,
|
||||
Ratio = 2f
|
||||
})
|
||||
.MeasureElement(new Size(500, 200))
|
||||
.ExpectChildMeasure(new Size(400, 200), new FullRender(100, 50))
|
||||
.CheckMeasureResult(new FullRender(400, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DrawChild_PerWidth()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitArea,
|
||||
Ratio = 2f
|
||||
})
|
||||
.DrawElement(new Size(500, 200))
|
||||
.ExpectChildDraw(new Size(400, 200))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DrawChild_PerHeight()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new AspectRatio
|
||||
{
|
||||
Child = x.CreateChild(),
|
||||
Option = AspectRatioOption.FitArea,
|
||||
Ratio = 2f
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildDraw(new Size(400, 200))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,11 +31,11 @@ namespace QuestPDF.UnitTests
|
||||
.For(x => new Background
|
||||
{
|
||||
Color = "#F00",
|
||||
Child = x.CreateChild("a")
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectCanvasDrawRectangle(new Position(0, 0), new Size(400, 300), "#F00")
|
||||
.ExpectChildDraw("a", new Size(400, 300))
|
||||
.ExpectChildDraw(new Size(400, 300))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@ namespace QuestPDF.UnitTests
|
||||
Bottom = 30,
|
||||
Left = 40,
|
||||
|
||||
Child = x.CreateChild("child")
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure("child", expectedInput: new Size(400, 300), returns: new FullRender(new Size(100, 50)))
|
||||
.ExpectChildMeasure(expectedInput: new Size(400, 300), returns: new FullRender(new Size(100, 50)))
|
||||
.CheckMeasureResult( new FullRender(new Size(100, 50)));
|
||||
}
|
||||
|
||||
@@ -43,15 +43,15 @@ namespace QuestPDF.UnitTests
|
||||
|
||||
Color = "#FF0000",
|
||||
|
||||
Child = x.CreateChild("child")
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectChildDraw("child", new Size(400, 300))
|
||||
.ExpectChildDraw(new Size(400, 300))
|
||||
.ExpectCanvasDrawRectangle(new Position(-20, -5), new Size(430, 10), "#FF0000") // top
|
||||
.ExpectCanvasDrawRectangle(new Position(-20, -5), new Size(40, 320), "#FF0000") // left
|
||||
.ExpectCanvasDrawRectangle(new Position(-20, 285), new Size(430, 30), "#FF0000") // bottom
|
||||
.ExpectCanvasDrawRectangle(new Position(390, -5), new Size(20, 320), "#FF0000") // right
|
||||
.ExpectChildDraw("child", new Size(400, 300))
|
||||
.ExpectChildDraw(new Size(400, 300))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,14 +15,29 @@ namespace QuestPDF.UnitTests
|
||||
[Test]
|
||||
public void Draw_ShouldHandleNullChild() => new Constrained().DrawWithoutChild();
|
||||
|
||||
#region Min Height
|
||||
|
||||
[Test]
|
||||
public void Measure_MinHeight_ExpectWrap()
|
||||
public void Measure_MinHeight_Empty()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MinHeight = 100
|
||||
})
|
||||
.MeasureElement(new Size(400, 150))
|
||||
.CheckMeasureResult(new FullRender(0, 100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_MinHeight_NotEnoughHeight()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MinHeight = 100,
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 50))
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
@@ -34,10 +49,10 @@ namespace QuestPDF.UnitTests
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MinHeight = 100,
|
||||
Child = x.CreateChild("a")
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 200))
|
||||
.ExpectChildMeasure("a", new Size(400, 200), new FullRender(400, 50))
|
||||
.ExpectChildMeasure(new Size(400, 200), new FullRender(400, 50))
|
||||
.CheckMeasureResult(new FullRender(400, 100));
|
||||
}
|
||||
|
||||
@@ -48,13 +63,17 @@ namespace QuestPDF.UnitTests
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MinHeight = 100,
|
||||
Child = x.CreateChild("a")
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 200))
|
||||
.ExpectChildMeasure("a", new Size(400, 200), new FullRender(400, 150))
|
||||
.ExpectChildMeasure(new Size(400, 200), new FullRender(400, 150))
|
||||
.CheckMeasureResult(new FullRender(400, 150));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Max Height
|
||||
|
||||
[Test]
|
||||
public void Measure_MaxHeight_Empty()
|
||||
{
|
||||
@@ -68,31 +87,183 @@ namespace QuestPDF.UnitTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_MaxHeight_PartialRender()
|
||||
public void Measure_MaxHeight_PassHeight()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MaxHeight = 100,
|
||||
Child = x.CreateChild("a")
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 200))
|
||||
.ExpectChildMeasure("a", new Size(400, 100), new PartialRender(400, 75))
|
||||
.CheckMeasureResult(new PartialRender(400, 75));
|
||||
.ExpectChildMeasure(new Size(400, 100), new FullRender(400, 75))
|
||||
.CheckMeasureResult(new FullRender(400, 75));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Min Width
|
||||
|
||||
[Test]
|
||||
public void Measure_MinWidth_Empty()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MinWidth = 100
|
||||
})
|
||||
.MeasureElement(new Size(150, 400))
|
||||
.CheckMeasureResult(new FullRender(100, 0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_MaxHeight_ExpectWrap()
|
||||
public void Measure_MinWidth_NotEnoughWidth()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MaxHeight = 100,
|
||||
Child = x.CreateChild("a")
|
||||
MinWidth = 100,
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 200))
|
||||
.ExpectChildMeasure("a", new Size(400, 100), new Wrap())
|
||||
.MeasureElement(new Size(50, 400))
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_MinWidth_ExtendWidth()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MinWidth = 100,
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 200))
|
||||
.ExpectChildMeasure(new Size(400, 200), new FullRender(50, 200))
|
||||
.CheckMeasureResult(new FullRender(100, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_MinWidth_PassWidth()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MinWidth = 100,
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 200))
|
||||
.ExpectChildMeasure(new Size(400, 200), new FullRender(150, 200))
|
||||
.CheckMeasureResult(new FullRender(150, 200));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Max Width
|
||||
|
||||
[Test]
|
||||
public void Measure_MaxWidth_Empty()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MaxWidth = 100
|
||||
})
|
||||
.MeasureElement(new Size(400, 150))
|
||||
.CheckMeasureResult(new FullRender(0, 0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_MaxWidth_PassWidth()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MaxWidth = 100,
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 200))
|
||||
.ExpectChildMeasure(new Size(100, 200), new FullRender(75, 200))
|
||||
.CheckMeasureResult(new FullRender(75, 200));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Space Plans
|
||||
|
||||
[Test]
|
||||
public void Measure_AcceptsFullRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 200))
|
||||
.ExpectChildMeasure(new Size(400, 200), new FullRender(300, 200))
|
||||
.CheckMeasureResult(new FullRender(300, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_AcceptsPartialRender()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 200))
|
||||
.ExpectChildMeasure(new Size(400, 200), new PartialRender(300, 200))
|
||||
.CheckMeasureResult(new PartialRender(300, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_AcceptsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.MeasureElement(new Size(400, 200))
|
||||
.ExpectChildMeasure(new Size(400, 200), new Wrap())
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drawing
|
||||
|
||||
[Test]
|
||||
public void Draw_Normal()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MaxWidth = 500,
|
||||
MaxHeight = 300,
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.DrawElement(new Size(400, 200))
|
||||
.ExpectChildDraw(new Size(400, 200))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_Shrink()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Constrained
|
||||
{
|
||||
MaxWidth = 300,
|
||||
MaxHeight = 150,
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
.DrawElement(new Size(400, 200))
|
||||
.ExpectChildDraw(new Size(300, 150))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,86 @@
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class DynamicImageTests
|
||||
{
|
||||
[Test]
|
||||
public void Measure_TakesAvailableSpaceRegardlessOfSize()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new DynamicImage
|
||||
{
|
||||
Source = GenerateImage
|
||||
})
|
||||
.MeasureElement(new Size(300, 200))
|
||||
.CheckMeasureResult(new FullRender(300, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_HandlesNull()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new DynamicImage
|
||||
{
|
||||
Source = size => null
|
||||
})
|
||||
.DrawElement(new Size(300, 200))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_PreservesSize()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new DynamicImage
|
||||
{
|
||||
Source = GenerateImage
|
||||
})
|
||||
.DrawElement(new Size(300, 200))
|
||||
.ExpectCanvasDrawImage(Position.Zero, new Size(300, 200))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_PassesCorrectSizeToSource()
|
||||
{
|
||||
Size passedSize = null;
|
||||
|
||||
TestPlan
|
||||
.For(x => new DynamicImage
|
||||
{
|
||||
Source = size =>
|
||||
{
|
||||
passedSize = size;
|
||||
return GenerateImage(size);
|
||||
}
|
||||
})
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectCanvasDrawImage(Position.Zero, new Size(400, 300))
|
||||
.CheckDrawResult();
|
||||
|
||||
passedSize.Should().Be(new Size(400, 300));
|
||||
}
|
||||
|
||||
byte[] GenerateImage(Size size)
|
||||
{
|
||||
var image = GenerateImage((int) size.Width, (int) size.Height);
|
||||
return image.Encode(SKEncodedImageFormat.Png, 100).ToArray();
|
||||
}
|
||||
|
||||
SKImage GenerateImage(int width, int height)
|
||||
{
|
||||
var imageInfo = new SKImageInfo(width, height);
|
||||
using var surface = SKSurface.Create(imageInfo);
|
||||
return surface.Snapshot();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class EmptyTests
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.UnitTests.TestEngine;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
@@ -9,17 +14,57 @@ namespace QuestPDF.UnitTests
|
||||
public class ImageTests
|
||||
{
|
||||
[Test]
|
||||
public void Draw_ShouldHandleNullChild()
|
||||
public void Measure_ShouldHandleNullChild() => new AspectRatio().MeasureWithoutChild();
|
||||
|
||||
[Test]
|
||||
public void Draw_ShouldHandleNullChild() => new AspectRatio().DrawWithoutChild();
|
||||
|
||||
[Test]
|
||||
public void Measure_TakesAvailableSpaceRegardlessOfSize()
|
||||
{
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
var image = new Image()
|
||||
TestPlan
|
||||
.For(x => new Image
|
||||
{
|
||||
Data = null
|
||||
};
|
||||
|
||||
image.Draw(It.IsAny<ICanvas>(), Size.Zero);
|
||||
});
|
||||
InternalImage = GenerateImage(400, 300)
|
||||
})
|
||||
.MeasureElement(new Size(300, 200))
|
||||
.CheckMeasureResult(new FullRender(300, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_TakesAvailableSpaceRegardlessOfSize()
|
||||
{
|
||||
TestPlan
|
||||
.For(x => new Image
|
||||
{
|
||||
InternalImage = GenerateImage(400, 300)
|
||||
})
|
||||
.DrawElement(new Size(300, 200))
|
||||
.ExpectCanvasDrawImage(new Position(0, 0), new Size(300, 200))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Fluent_RecognizesImageProportions()
|
||||
{
|
||||
var image = GenerateImage(600, 200).Encode(SKEncodedImageFormat.Png, 100).ToArray();
|
||||
|
||||
TestPlan
|
||||
.For(x =>
|
||||
{
|
||||
var container = new Container();
|
||||
container.Image(image);
|
||||
return container;
|
||||
})
|
||||
.MeasureElement(new Size(300, 200))
|
||||
.CheckMeasureResult(new FullRender(300, 100));;
|
||||
}
|
||||
|
||||
SKImage GenerateImage(int width, int height)
|
||||
{
|
||||
var imageInfo = new SKImageInfo(width, height);
|
||||
using var surface = SKSurface.Create(imageInfo);
|
||||
return surface.Snapshot();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,62 +17,77 @@ namespace QuestPDF.UnitTests
|
||||
[Test]
|
||||
public void Draw_ShouldHandleNullChild() => new Padding().DrawWithoutChild();
|
||||
|
||||
private Padding GetPadding(Element child)
|
||||
private Padding GetPadding(TestPlan plan)
|
||||
{
|
||||
return new Padding()
|
||||
{
|
||||
Top = 5,
|
||||
Right = 10,
|
||||
Bottom = 15,
|
||||
Left = 20,
|
||||
Top = 10,
|
||||
Right = 20,
|
||||
Bottom = 30,
|
||||
Left = 40,
|
||||
|
||||
Child = child
|
||||
Child = plan.CreateChild()
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_WhenChildReturnsWrap_ReturnsWrap()
|
||||
public void Measure_General_EnoughSpace()
|
||||
{
|
||||
var child = new Mock<Element>();
|
||||
|
||||
child
|
||||
.Setup(x => x.Measure(It.IsAny<Size>()))
|
||||
.Returns(() => new Wrap());
|
||||
|
||||
GetPadding(child.Object)
|
||||
.Measure(Size.Zero)
|
||||
.Should()
|
||||
.BeOfType<Wrap>();
|
||||
TestPlan
|
||||
.For(GetPadding)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(new Size(340, 260), new FullRender(140, 60))
|
||||
.CheckMeasureResult(new FullRender(200, 100));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_NotEnoughWidth()
|
||||
{
|
||||
TestPlan
|
||||
.For(GetPadding)
|
||||
.MeasureElement(new Size(50, 300))
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_WhenChildReturnsFullRender_ReturnsFullRender()
|
||||
public void Measure_NotEnoughHeight()
|
||||
{
|
||||
var child = new Mock<Element>();
|
||||
|
||||
child
|
||||
.Setup(x => x.Measure(It.IsAny<Size>()))
|
||||
.Returns(() => new FullRender(Size.Zero));
|
||||
|
||||
GetPadding(child.Object)
|
||||
.Measure(Size.Zero)
|
||||
.Should()
|
||||
.BeOfType<FullRender>();
|
||||
TestPlan
|
||||
.For(GetPadding)
|
||||
.MeasureElement(new Size(20, 300))
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_WhenChildReturnsPartialRender_ReturnsPartialRender()
|
||||
public void Measure_AcceptsPartialRender()
|
||||
{
|
||||
var child = new Mock<Element>();
|
||||
|
||||
child
|
||||
.Setup(x => x.Measure(It.IsAny<Size>()))
|
||||
.Returns(() => new PartialRender(Size.Zero));
|
||||
|
||||
GetPadding(child.Object)
|
||||
.Measure(Size.Zero)
|
||||
.Should()
|
||||
.BeOfType<PartialRender>();
|
||||
TestPlan
|
||||
.For(GetPadding)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(new Size(340, 260), new PartialRender(40, 160))
|
||||
.CheckMeasureResult(new PartialRender(100, 200));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Measure_AcceptsWrap()
|
||||
{
|
||||
TestPlan
|
||||
.For(GetPadding)
|
||||
.MeasureElement(new Size(400, 300))
|
||||
.ExpectChildMeasure(new Size(340, 260), new Wrap())
|
||||
.CheckMeasureResult(new Wrap());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Draw_General()
|
||||
{
|
||||
TestPlan
|
||||
.For(GetPadding)
|
||||
.DrawElement(new Size(400, 300))
|
||||
.ExpectCanvasTranslate(new Position(40, 10))
|
||||
.ExpectChildDraw(new Size(340, 260))
|
||||
.ExpectCanvasTranslate(new Position(-40, -10))
|
||||
.CheckDrawResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class PageableColumnTests
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace QuestPDF.UnitTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class PlaceholderTests
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ namespace QuestPDF.UnitTests
|
||||
TestPlan
|
||||
.For(x => new ShowOnce()
|
||||
{
|
||||
Child = x.CreateChild("child")
|
||||
Child = x.CreateChild()
|
||||
})
|
||||
|
||||
// Measure the element and return result
|
||||
@@ -52,21 +52,21 @@ namespace QuestPDF.UnitTests
|
||||
|
||||
// Draw element partially
|
||||
.DrawElement(new Size(200, 200))
|
||||
.ExpectChildMeasure("child", new Size(200, 200), new PartialRender(new Size(200, 200)))
|
||||
.ExpectChildDraw("child", new Size(200, 200))
|
||||
.ExpectChildMeasure(new Size(200, 200), new PartialRender(new Size(200, 200)))
|
||||
.ExpectChildDraw(new Size(200, 200))
|
||||
.CheckDrawResult()
|
||||
|
||||
// Element was not fully drawn
|
||||
// It should be measured again for rendering on next page
|
||||
.MeasureElement(new Size(800, 200))
|
||||
.ExpectChildMeasure("child", new Size(800, 200), new FullRender(new Size(400, 200)))
|
||||
.ExpectChildMeasure(new Size(800, 200), new FullRender(new Size(400, 200)))
|
||||
.CheckMeasureResult(new FullRender(new Size(400, 200)))
|
||||
|
||||
// Draw element on next page
|
||||
// Element was fully drawn at this point
|
||||
.DrawElement(new Size(400, 200))
|
||||
.ExpectChildMeasure("child", new Size(400, 200), new FullRender(new Size(400, 200)))
|
||||
.ExpectChildDraw("child", new Size(400, 200))
|
||||
.ExpectChildMeasure(new Size(400, 200), new FullRender(new Size(400, 200)))
|
||||
.ExpectChildDraw(new Size(400, 200))
|
||||
.CheckDrawResult()
|
||||
|
||||
// In the next attempt of measuring element, it should behave like empty parent.
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
{
|
||||
internal class TestPlan
|
||||
{
|
||||
private const string DefaultChildName = "child";
|
||||
|
||||
private Element Element { get; set; }
|
||||
private ICanvas Canvas { get; }
|
||||
|
||||
@@ -100,6 +102,8 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Element CreateChild() => CreateChild(DefaultChildName);
|
||||
|
||||
public Element CreateChild(string id)
|
||||
{
|
||||
@@ -153,11 +157,21 @@ namespace QuestPDF.UnitTests.TestEngine
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestPlan ExpectChildMeasure(Size expectedInput, ISpacePlan returns)
|
||||
{
|
||||
return ExpectChildMeasure(DefaultChildName, expectedInput, returns);
|
||||
}
|
||||
|
||||
public TestPlan ExpectChildMeasure(string child, Size expectedInput, ISpacePlan returns)
|
||||
{
|
||||
return AddOperation(new ChildMeasureOperationBase(child, expectedInput, returns));
|
||||
}
|
||||
|
||||
public TestPlan ExpectChildDraw(Size expectedInput)
|
||||
{
|
||||
return ExpectChildDraw(DefaultChildName, expectedInput);
|
||||
}
|
||||
|
||||
public TestPlan ExpectChildDraw(string child, Size expectedInput)
|
||||
{
|
||||
return AddOperation(new ChildDrawOperationBase(child, expectedInput));
|
||||
|
||||
@@ -35,8 +35,6 @@ namespace QuestPDF.Drawing
|
||||
catch (Exception exception)
|
||||
{
|
||||
pdf.Close();
|
||||
stream.Close();
|
||||
|
||||
throw new DocumentDrawingException("An exception occured during document drawing.", exception);
|
||||
}
|
||||
|
||||
@@ -45,8 +43,6 @@ namespace QuestPDF.Drawing
|
||||
if (totalPages >= metadata.DocumentLayoutExceptionThreshold)
|
||||
{
|
||||
pdf.Close();
|
||||
stream.Close();
|
||||
|
||||
throw new DocumentLayoutException("Composed layout generates infinite document.");
|
||||
}
|
||||
|
||||
@@ -57,7 +53,6 @@ namespace QuestPDF.Drawing
|
||||
}
|
||||
|
||||
pdf.Close();
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
internal static IEnumerable<byte[]> GenerateImages(IDocument document)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
@@ -6,18 +7,33 @@ namespace QuestPDF.Elements
|
||||
internal class AspectRatio : ContainerElement
|
||||
{
|
||||
public float Ratio { get; set; } = 1;
|
||||
public AspectRatioOption Option { get; set; } = AspectRatioOption.FitWidth;
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if(Child == null)
|
||||
return new FullRender(Size.Zero);
|
||||
|
||||
var size = GetSize(availableSpace);
|
||||
var targetSize = GetTargetSize(availableSpace);
|
||||
|
||||
if (size.Height > availableSpace.Height + Size.Epsilon)
|
||||
if (targetSize.Height > availableSpace.Height + Size.Epsilon)
|
||||
return new Wrap();
|
||||
|
||||
return new FullRender(size);
|
||||
if (targetSize.Width > availableSpace.Width + Size.Epsilon)
|
||||
return new Wrap();
|
||||
|
||||
var childSize = Child.Measure(targetSize);
|
||||
|
||||
if (childSize is Wrap)
|
||||
return new Wrap();
|
||||
|
||||
if (childSize is PartialRender)
|
||||
return new PartialRender(targetSize);
|
||||
|
||||
if (childSize is FullRender)
|
||||
return new FullRender(targetSize);
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
@@ -25,17 +41,24 @@ namespace QuestPDF.Elements
|
||||
if (Child == null)
|
||||
return;
|
||||
|
||||
var size = GetSize(availableSpace);
|
||||
|
||||
if (size.Height > availableSpace.Height)
|
||||
return;
|
||||
|
||||
Child.Draw(canvas, size);
|
||||
var size = GetTargetSize(availableSpace);
|
||||
Child?.Draw(canvas, size);
|
||||
}
|
||||
|
||||
private Size GetSize(Size availableSpace)
|
||||
private Size GetTargetSize(Size availableSpace)
|
||||
{
|
||||
return new Size(availableSpace.Width, (int)(availableSpace.Width / Ratio));
|
||||
var spaceRatio = availableSpace.Width / availableSpace.Height;
|
||||
|
||||
var fitHeight = new Size(availableSpace.Height * Ratio, availableSpace.Height) ;
|
||||
var fitWidth = new Size(availableSpace.Width, availableSpace.Width / Ratio);
|
||||
|
||||
return Option switch
|
||||
{
|
||||
AspectRatioOption.FitWidth => fitWidth,
|
||||
AspectRatioOption.FitHeight => fitHeight,
|
||||
AspectRatioOption.FitArea => Ratio < spaceRatio ? fitHeight : fitWidth,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,15 +31,15 @@ namespace QuestPDF.Elements
|
||||
if (measurement is Wrap)
|
||||
return new Wrap();
|
||||
|
||||
var actualSize = new Size(
|
||||
var targetSize = new Size(
|
||||
MathHelpers.Max(MinWidth, size.Width),
|
||||
MathHelpers.Max(MinHeight, size.Height));
|
||||
|
||||
if (size is FullRender)
|
||||
return new FullRender(actualSize);
|
||||
return new FullRender(targetSize);
|
||||
|
||||
if (size is PartialRender)
|
||||
return new PartialRender(actualSize);
|
||||
return new PartialRender(targetSize);
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal Container()
|
||||
{
|
||||
Child = new Empty();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
-14
@@ -1,25 +1,37 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Debug : ContainerElement
|
||||
{
|
||||
private static readonly TextStyle TextStyle = TextStyle.Default.Color("#FF0000").FontType("Consolas").Size(10);
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
{
|
||||
var textStyle = new TextStyle
|
||||
{
|
||||
Color = "#FF0000",
|
||||
FontType = "Consolas",
|
||||
Size = 10
|
||||
};
|
||||
|
||||
Child?.Draw(canvas, availableSpace);
|
||||
|
||||
canvas.DrawRectangle(Position.Zero, availableSpace, "#FF0000");
|
||||
canvas.DrawRectangle(Position.Zero, availableSpace, "#FF00FF");
|
||||
|
||||
canvas.DrawText($"W: {availableSpace.Width}", new Position(5, 12), textStyle);
|
||||
canvas.DrawText($"H: {availableSpace.Height}", new Position(5, 22), textStyle);
|
||||
DrawBoundingBox();
|
||||
DrawDimensions();
|
||||
|
||||
void DrawBoundingBox()
|
||||
{
|
||||
// TODO: when layer element is done, move this code into fluent API
|
||||
|
||||
var container = new Container();
|
||||
|
||||
container
|
||||
.Border(1)
|
||||
.BorderColor("#FF0000")
|
||||
.Background("#33FF0000");
|
||||
|
||||
container.Draw(canvas, availableSpace);
|
||||
}
|
||||
|
||||
void DrawDimensions()
|
||||
{
|
||||
canvas.DrawText($"W: {availableSpace.Width:F1}", new Position(5, 12), TextStyle);
|
||||
canvas.DrawText($"H: {availableSpace.Height:F1}", new Position(5, 22), TextStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using QuestPDF.Drawing.SpacePlan;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Elements
|
||||
{
|
||||
@@ -10,17 +11,19 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
if (availableSpace.Width < 0 || availableSpace.Height < 0)
|
||||
return new Wrap();
|
||||
|
||||
return new FullRender(availableSpace.Width, availableSpace.Height);
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
{
|
||||
var imageElement = new Image()
|
||||
var imageData = Source?.Invoke(availableSpace);
|
||||
|
||||
if (imageData == null)
|
||||
return;
|
||||
|
||||
var imageElement = new Image
|
||||
{
|
||||
Data = Source?.Invoke(availableSpace)
|
||||
InternalImage = SKImage.FromEncodedData(imageData)
|
||||
};
|
||||
|
||||
imageElement.Draw(canvas, availableSpace);
|
||||
|
||||
@@ -6,53 +6,24 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Image : Element
|
||||
{
|
||||
public byte[]? Data { get; set; }
|
||||
private SKImage? InternalImage { get; set; }
|
||||
public SKImage? InternalImage { get; set; }
|
||||
|
||||
~Image()
|
||||
{
|
||||
InternalImage?.Dispose();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (Data != null)
|
||||
InternalImage ??= SKImage.FromEncodedData(Data);
|
||||
}
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
Initialize();
|
||||
|
||||
if (InternalImage == null)
|
||||
return new FullRender(Size.Zero);
|
||||
|
||||
if (availableSpace.Width < 0 || availableSpace.Height < 0)
|
||||
return new Wrap();
|
||||
|
||||
var size = GetTargetSize(availableSpace);
|
||||
return new FullRender(size);
|
||||
return new FullRender(availableSpace);
|
||||
}
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
{
|
||||
Initialize();
|
||||
|
||||
if (InternalImage == null)
|
||||
return;
|
||||
|
||||
var size = GetTargetSize(availableSpace);
|
||||
canvas.DrawImage(InternalImage, Position.Zero, size);
|
||||
}
|
||||
|
||||
private Size GetTargetSize(Size availableSpace)
|
||||
{
|
||||
var imageRatio = InternalImage.Width / (float)InternalImage.Height;
|
||||
var spaceRatio = availableSpace.Width / (float) availableSpace.Height;
|
||||
|
||||
return imageRatio < spaceRatio
|
||||
? new Size((int)(availableSpace.Height * imageRatio), availableSpace.Height)
|
||||
: new Size(availableSpace.Width, (int)(availableSpace.Width / imageRatio));
|
||||
canvas.DrawImage(InternalImage, Position.Zero, availableSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,10 @@ namespace QuestPDF.Elements
|
||||
return new FullRender(Size.Zero);
|
||||
|
||||
var internalSpace = InternalSpace(availableSpace);
|
||||
|
||||
if (internalSpace.Width <= 0 || internalSpace.Height <= 0)
|
||||
return new Wrap();
|
||||
|
||||
var measure = Child.Measure(internalSpace) as Size;
|
||||
|
||||
if (measure == null)
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace QuestPDF.Elements
|
||||
.AlignMiddle()
|
||||
.AlignCenter()
|
||||
.MaxHeight(32)
|
||||
.Image(ImageData);
|
||||
.Image(ImageData, ImageScaling.FitArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,10 +8,7 @@ namespace QuestPDF.Elements
|
||||
|
||||
internal override void Draw(ICanvas canvas, Size availableSpace)
|
||||
{
|
||||
if (Child == null)
|
||||
return;
|
||||
|
||||
Child.Draw(canvas, availableSpace);
|
||||
Child?.Draw(canvas, availableSpace);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ namespace QuestPDF.Elements
|
||||
{
|
||||
internal class Section : Element
|
||||
{
|
||||
public Element? Header { get; set; }
|
||||
public Element? Content { get; set; }
|
||||
public ContainerElement? Header { get; set; }
|
||||
public ContainerElement? Content { get; set; }
|
||||
|
||||
internal override ISpacePlan Measure(Size availableSpace)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using QuestPDF.Drawing;
|
||||
using QuestPDF.Drawing.Exceptions;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
@@ -33,29 +34,21 @@ namespace QuestPDF.Fluent
|
||||
return child;
|
||||
}
|
||||
|
||||
public static void Element(this IContainer element, Action<IContainer> handler)
|
||||
public static void Element<TParent>(this TParent parent, Action<IContainer> handler) where TParent : IContainer
|
||||
{
|
||||
var container = new Container();
|
||||
element.Element(container);
|
||||
handler?.Invoke(container);
|
||||
handler(parent.Container());
|
||||
}
|
||||
|
||||
public static IContainer Element<TParent>(this TParent parent, Func<IContainer, IContainer> handler) where TParent : IContainer
|
||||
{
|
||||
return handler(parent.Container()).Container();
|
||||
}
|
||||
|
||||
public static IContainer Debug(this IContainer parent)
|
||||
{
|
||||
return parent.Element(new Debug());
|
||||
}
|
||||
|
||||
public static void Image(this IContainer element, byte[] data)
|
||||
{
|
||||
element.Element(new Image
|
||||
{
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
||||
public static void DynamicImage(this IContainer element, Func<Size, byte[]> imageSource)
|
||||
{
|
||||
element.Element(new DynamicImage
|
||||
{
|
||||
Source = imageSource
|
||||
});
|
||||
}
|
||||
|
||||
public static void PageNumber(this IContainer element, string textFormat = "{number}", TextStyle? style = null)
|
||||
{
|
||||
element.Element(new PageNumber
|
||||
@@ -65,11 +58,12 @@ namespace QuestPDF.Fluent
|
||||
});
|
||||
}
|
||||
|
||||
public static IContainer AspectRatio(this IContainer element, float ratio)
|
||||
public static IContainer AspectRatio(this IContainer element, float ratio, AspectRatioOption option = AspectRatioOption.FitWidth)
|
||||
{
|
||||
return element.Element(new AspectRatio
|
||||
{
|
||||
Ratio = ratio
|
||||
Ratio = ratio,
|
||||
Option = option
|
||||
});
|
||||
}
|
||||
|
||||
@@ -98,7 +92,10 @@ namespace QuestPDF.Fluent
|
||||
style ??= TextStyle.Default;
|
||||
|
||||
if (element is Alignment alignment)
|
||||
{
|
||||
style = style.Clone();
|
||||
style.Alignment = alignment.Horizontal;
|
||||
}
|
||||
|
||||
element.Element(new Text()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using QuestPDF.Elements;
|
||||
using QuestPDF.Infrastructure;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace QuestPDF.Fluent
|
||||
{
|
||||
public static class ImageExtensions
|
||||
{
|
||||
public static void Image(this IContainer parent, byte[] data, ImageScaling scaling = ImageScaling.FitWidth)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
var image = SKImage.FromEncodedData(data);
|
||||
var aspectRatio = image.Width / (float)image.Height;
|
||||
|
||||
var imageElement = new Image
|
||||
{
|
||||
InternalImage = image
|
||||
};
|
||||
|
||||
if (scaling != ImageScaling.Resize)
|
||||
parent = parent.AspectRatio(aspectRatio, Map(scaling));
|
||||
|
||||
parent.Element(imageElement);
|
||||
|
||||
static AspectRatioOption Map(ImageScaling scaling)
|
||||
{
|
||||
return scaling switch
|
||||
{
|
||||
ImageScaling.FitWidth => AspectRatioOption.FitWidth,
|
||||
ImageScaling.FitHeight => AspectRatioOption.FitHeight,
|
||||
ImageScaling.FitArea => AspectRatioOption.FitArea,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void DynamicImage(this IContainer element, Func<Size, byte[]> imageSource)
|
||||
{
|
||||
element.Element(new DynamicImage
|
||||
{
|
||||
Source = imageSource
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,69 @@
|
||||
using QuestPDF.Infrastructure;
|
||||
using System;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace QuestPDF.Helpers
|
||||
{
|
||||
public class PageSize : Size
|
||||
{
|
||||
public PageSize(float width, float height) : base(width, height)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static class PageSizes
|
||||
{
|
||||
public const int PointsPerInch = 72;
|
||||
|
||||
public static Size A0 => new Size(2384, 3370);
|
||||
public static Size A1 => new Size(1684, 2384);
|
||||
public static Size A2 => new Size(1190, 1684);
|
||||
public static Size A3 => new Size(842, 1190);
|
||||
public static Size A4 => new Size(595, 842);
|
||||
public static Size A5 => new Size(420, 595);
|
||||
public static Size A6 => new Size(298, 420);
|
||||
public static Size A7 => new Size(210, 298);
|
||||
public static Size A8 => new Size(148, 210);
|
||||
|
||||
public static Size B0 => new Size(2835, 4008);
|
||||
public static Size B1 => new Size(2004, 2835);
|
||||
public static Size B2 => new Size(1417, 2004);
|
||||
public static Size B3 => new Size(1001, 1417);
|
||||
public static Size B4 => new Size(709, 1001);
|
||||
public static Size B5 => new Size(499, 709);
|
||||
public static Size B6 => new Size(354, 499);
|
||||
public static Size B7 => new Size(249, 354);
|
||||
public static Size B8 => new Size(176, 249);
|
||||
public static Size B9 => new Size(125, 176);
|
||||
public static Size B10 => new Size(88, 125);
|
||||
|
||||
public static Size A0 => new PageSize(2384.2f, 3370.8f);
|
||||
public static Size A1 => new PageSize(1684, 2384.2f);
|
||||
public static Size A2 => new PageSize(1190.7f, 1684);
|
||||
public static Size A3 => new PageSize(842, 1190.7f);
|
||||
public static Size A4 => new PageSize(595.4f, 842);
|
||||
public static Size A5 => new PageSize(419.6f, 595.4f);
|
||||
public static Size A6 => new PageSize(297.7f, 419.6f);
|
||||
public static Size A7 => new PageSize(209.8f, 297.7f);
|
||||
public static Size A8 => new PageSize(147.4f, 209.8f);
|
||||
public static Size A9 => new PageSize(104.9f, 147.4f);
|
||||
public static Size A10 => new PageSize(73.7f, 104.9f);
|
||||
|
||||
public static Size B0 => new PageSize(2835, 4008.7f);
|
||||
public static Size B1 => new PageSize(2004.3f, 2835);
|
||||
public static Size B2 => new PageSize(1417.5f, 2004.3f);
|
||||
public static Size B3 => new PageSize(1000.8f, 1417.5f);
|
||||
public static Size B4 => new PageSize(708.8f, 1000.8f);
|
||||
public static Size B5 => new PageSize(499, 708.8f);
|
||||
public static Size B6 => new PageSize(354.4f, 499);
|
||||
public static Size B7 => new PageSize(249.5f, 354.4f);
|
||||
public static Size B8 => new PageSize(175.8f, 249.5f);
|
||||
public static Size B9 => new PageSize(124.7f, 175.8f);
|
||||
public static Size B10 => new PageSize(87.9f, 124.7f);
|
||||
|
||||
public static Size Env10 => new PageSize(683.2f, 294.8f);
|
||||
public static Size EnvC4 => new PageSize(649.2f, 918.5f);
|
||||
public static Size EnvDL => new PageSize(311.9f, 623.7f);
|
||||
|
||||
public static Size Executive => new PageSize(522, 756);
|
||||
public static Size Legal => new PageSize(612.4f, 1009.3f);
|
||||
public static Size Letter => new PageSize(612.4f, 791);
|
||||
|
||||
public static Size ARCHA => new PageSize(649.2f, 864.7f);
|
||||
public static Size ARCHB => new PageSize(864.7f, 1295.6f);
|
||||
public static Size ARCHC => new PageSize(1295.6f, 1729.3f);
|
||||
public static Size ARCHD => new PageSize(1729.3f, 2591.2f);
|
||||
public static Size ARCHE => new PageSize(2591.2f, 3455.9f);
|
||||
public static Size ARCH_E1 => new PageSize(2160.3f, 3024.9f);
|
||||
public static Size ARCH_E2 => new PageSize(1871.1f, 2735.8f);
|
||||
public static Size ARCH_E3 => new PageSize(1944.8f, 2809.5f);
|
||||
|
||||
public static PageSize Portrait(this Size size)
|
||||
{
|
||||
return new PageSize(Math.Min(size.Width, size.Height), Math.Max(size.Width, size.Height));
|
||||
}
|
||||
|
||||
public static PageSize Landscape(this Size size)
|
||||
{
|
||||
return new PageSize(Math.Max(size.Width, size.Height), Math.Min(size.Width, size.Height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public enum AspectRatioOption
|
||||
{
|
||||
FitWidth,
|
||||
FitHeight,
|
||||
FitArea
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace QuestPDF.Infrastructure
|
||||
{
|
||||
public enum ImageScaling
|
||||
{
|
||||
FitWidth,
|
||||
FitHeight,
|
||||
FitArea,
|
||||
Resize
|
||||
}
|
||||
}
|
||||
+17
-11
@@ -4,9 +4,16 @@
|
||||
<Authors>MarcinZiabek</Authors>
|
||||
<Company>CodeFlint</Company>
|
||||
<PackageId>QuestPDF</PackageId>
|
||||
<Version>2021.3.0</Version>
|
||||
<Version>2021.4.0</Version>
|
||||
<PackageDescription>QuestPDF is an open-source, modern and battle-tested library that can help you with generating PDF documents by offering friendly, discoverable and predictable C# fluent API.</PackageDescription>
|
||||
<PackageReleaseNotes>Generating images functionality, ShowIf element, fixed mutating TextStyle</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>
|
||||
1. Added new element: `Debug` - this element can be used for inspecting its children and space taken by them,
|
||||
2. Added new element: `Element` can be used for injecting dynamic elements (usually conditionally) without breaking the fluent API chain. This is practically a syntactic sugar to simplify your code.
|
||||
3. The `AspectRatio` element allows now to specify scaling rule: fitting width, height or available space.
|
||||
4. The `Image` element supports now scaling rules: fitting width, height or available space, as well as scaling unproportionally.
|
||||
5. Added more, commonly used page sizes (thank you for your work [lmingle](https://github.com/lmingle)).
|
||||
6. Bug fix: the `GeneratePdf(Stream stream)` method does not close the output stream (thank you for your suggestion [tthiery](https://github.com/tthiery)).
|
||||
</PackageReleaseNotes>
|
||||
<LangVersion>8</LangVersion>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageIcon>Logo.png</PackageIcon>
|
||||
@@ -22,17 +29,16 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.2" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.80.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\ImagePlaceholder.png" />
|
||||
<None Remove="ImagePlaceholder.png" />
|
||||
<None Include="Resources\Logo.png">
|
||||
<Pack>true</Pack>
|
||||
<Visible>false</Visible>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
<EmbeddedResource Include="Resources\ImagePlaceholder.png" />
|
||||
<None Remove="ImagePlaceholder.png" />
|
||||
<None Include="Resources\Logo.png">
|
||||
<Pack>true</Pack>
|
||||
<Visible>false</Visible>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -26,12 +26,53 @@ The library is available as a nuget package. You can install it as any other nug
|
||||
</a>
|
||||
|
||||
|
||||
## Write less, achieve more
|
||||
## Documentation
|
||||
|
||||
1. [Release notes and roadmap](https://www.questpdf.com/documentation/releases.html) - everything that is planned for future library iterations, description of new features and information about potential breaking changes.
|
||||
2. [Getting started tutorial](https://www.questpdf.com/documentation/getting-started.html) - a short and easy to follow tutorial showing how to design an invoice document under 200 lines of code.
|
||||
3. [API Reference](https://www.questpdf.com/documentation/api-reference.html) - a detailed description of behavior of all available components and how to use them with C# Fluent API.
|
||||
4. [Patterns and practices](https://www.questpdf.com/documentation/patterns-and-practices.html#document-metadata) - everything that may help you designing great reports and reusable code that is easy to maintain.
|
||||
|
||||
## Example invoice
|
||||
|
||||
Do you believe that creating a complete invoice document can take less than 200 lines of code? We have prepared for you a step-by-step instruction that shows every detail of this implementation and describes the best patterns and practices.
|
||||
|
||||
For tutorial, documentation and API reference, please visit [the QuestPDF documentation](https://www.questpdf.com/documentation/getting-started.html).
|
||||
|
||||
This repository contains sample implementation of the full, page-aware invoice similar to the one below. Let's get started!
|
||||
<a href="https://github.com/QuestPDF/example-invoice">
|
||||
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/invoice.png" width="595px">
|
||||
</a>
|
||||
|
||||
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/invoice.png" width="595px">
|
||||
Here you can find an example code showing how easy is to write and understand the fluent API:
|
||||
|
||||
```csharp
|
||||
public void Compose(IContainer container)
|
||||
{
|
||||
container
|
||||
.PaddingHorizontal(50)
|
||||
.PaddingVertical(50)
|
||||
.Page(page =>
|
||||
{
|
||||
page.Header(ComposeHeader);
|
||||
page.Content(ComposeContent);
|
||||
page.Footer().AlignCenter().PageNumber("Page {number}");
|
||||
});
|
||||
}
|
||||
|
||||
void ComposeHeader(IContainer container)
|
||||
{
|
||||
container.Row(row =>
|
||||
{
|
||||
row.RelativeColumn().Stack(column =>
|
||||
{
|
||||
column.Element().Text($"Invoice #{Model.InvoiceNumber}", TextStyle.Default.Size(20).Bold());
|
||||
column.Element().Text($"Issue date: {Model.IssueDate:d}");
|
||||
column.Element().Text($"Due date: {Model.DueDate:d}");
|
||||
});
|
||||
|
||||
row.ConstantColumn(100).Height(50).Placeholder();
|
||||
});
|
||||
}
|
||||
|
||||
// code describing content
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user