2021.2.0 Internal links, external links, dynamic images, font weights

This commit is contained in:
Marcin Ziąbek 2021-02-08 14:36:12 +01:00
Родитель 8b98c4b1ac
Коммит 35214892ef
41 изменённых файлов: 512 добавлений и 322 удалений

Просмотреть файл

@ -25,11 +25,11 @@ namespace QuestPDF.ReportSample.Layouts
.PaddingBottom(5) .PaddingBottom(5)
.Text(Model.Title, Typography.Headline); .Text(Model.Title, Typography.Headline);
section.Content().PageableStack(column => section.Content().PageableStack(stack =>
{ {
foreach (var part in Model.Parts) foreach (var part in Model.Parts)
{ {
column.Element().Row(row => stack.Element().Row(row =>
{ {
row.ConstantColumn(150).DarkCell().Text(part.Label, Typography.Normal); row.ConstantColumn(150).DarkCell().Text(part.Label, Typography.Normal);
var frame = row.RelativeColumn().LightCell(); var frame = row.RelativeColumn().LightCell();

Просмотреть файл

@ -71,7 +71,7 @@ namespace QuestPDF.ReportSample.Layouts
}); });
row.ConstantColumn(150).Image(Model.LogoData); row.ConstantColumn(150).ExternalLink("https://www.questpdf.com").Image(Model.LogoData);
}); });
} }
@ -81,11 +81,14 @@ namespace QuestPDF.ReportSample.Layouts
{ {
stack.Spacing(20); stack.Spacing(20);
stack.Element().Component(new TableOfContentsTemplate(Model.Sections));
foreach (var section in Model.Sections) foreach (var section in Model.Sections)
stack.Element().Component(new SectionTemplate(section)); stack.Element().Location(section.Title).Component(new SectionTemplate(section));
stack.Element().PageBreak(); stack.Element().PageBreak();
stack.Element().Location("Photos");
foreach (var photo in Model.Photos) foreach (var photo in Model.Photos)
stack.Element().Component(new PhotoTemplate(photo)); stack.Element().Component(new PhotoTemplate(photo));
}); });

Просмотреть файл

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
namespace QuestPDF.ReportSample.Layouts
{
public class TableOfContentsTemplate : IComponent
{
private List<ReportSection> Sections { get; }
public TableOfContentsTemplate(List<ReportSection> sections)
{
Sections = sections;
}
public void Compose(IContainer container)
{
container
.Section(section =>
{
section
.Header()
.PaddingBottom(5)
.Text("Table of contents", Typography.Headline);
section.Content().PageableStack(stack =>
{
stack.Spacing(5);
for (var i = 0; i < Sections.Count; i++)
stack.Element(c => DrawLink(c, i+1, Sections[i].Title));
stack.Element(c => DrawLink(c, Sections.Count+1, "Photos"));
});
});
}
private void DrawLink(IContainer container, int number, string locationName)
{
container
.InternalLink(locationName)
.Row(row =>
{
row.ConstantColumn(25).Text($"{number}.", Typography.Normal);
row.RelativeColumn().Text(locationName, Typography.Normal);
});
}
}
}

Просмотреть файл

@ -5,7 +5,7 @@ namespace QuestPDF.ReportSample
{ {
public static class Typography public static class Typography
{ {
public static TextStyle Title => TextStyle.Default.FontType("Helvetica").Color("#000000").Size(20); 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 Headline => TextStyle.Default.FontType("Helvetica").Color("#047AED").Size(14);
public static TextStyle Normal => TextStyle.Default.FontType("Helvetica").Color("#000000").Size(10).LineHeight(1.25f).AlignLeft(); public static TextStyle Normal => TextStyle.Default.FontType("Helvetica").Color("#000000").Size(10).LineHeight(1.25f).AlignLeft();
} }

Просмотреть файл

@ -2,7 +2,7 @@
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.MeasureTest; using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {

Просмотреть файл

@ -3,7 +3,7 @@ using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.MeasureTest; using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {

Просмотреть файл

@ -1,7 +1,7 @@
using NUnit.Framework; using NUnit.Framework;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.MeasureTest; using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {

Просмотреть файл

@ -2,7 +2,7 @@
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.MeasureTest; using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {

Просмотреть файл

@ -2,7 +2,7 @@
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.MeasureTest; using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {

Просмотреть файл

@ -1,6 +1,6 @@
using NUnit.Framework; using NUnit.Framework;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.UnitTests.MeasureTest; using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {

Просмотреть файл

@ -1,6 +1,6 @@
using NUnit.Framework; using NUnit.Framework;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.UnitTests.MeasureTest; using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {

Просмотреть файл

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using FluentAssertions; using FluentAssertions;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.MeasureTest; using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {
@ -13,7 +13,7 @@ namespace QuestPDF.UnitTests
public static Size RandomSize => new Size(Random.Next(200, 400), Random.Next(100, 200)); public static Size RandomSize => new Size(Random.Next(200, 400), Random.Next(100, 200));
public static void ShouldEqual(this IEnumerable<Operation> commands, IEnumerable<Operation> expected) public static void ShouldEqual(this IEnumerable<OperationBase> commands, IEnumerable<OperationBase> expected)
{ {
commands.Should().HaveSameCount(expected); commands.Should().HaveSameCount(expected);

Просмотреть файл

@ -4,7 +4,7 @@ using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.MeasureTest; using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {

Просмотреть файл

@ -3,7 +3,7 @@ using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.MeasureTest; using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {

Просмотреть файл

@ -2,7 +2,7 @@
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using QuestPDF.UnitTests.MeasureTest; using QuestPDF.UnitTests.TestEngine;
namespace QuestPDF.UnitTests namespace QuestPDF.UnitTests
{ {
@ -220,6 +220,9 @@ namespace QuestPDF.UnitTests
.ExpectCanvasTranslate(0, -250) .ExpectCanvasTranslate(0, -250)
.ExpectChildMeasure("c", expectedInput: new Size(500, 400), returns: new FullRender(Size.Zero)) .ExpectChildMeasure("c", expectedInput: new Size(500, 400), returns: new FullRender(Size.Zero))
.ExpectCanvasTranslate(0, 600)
.ExpectChildDraw("c", new Size(500, 0))
.ExpectCanvasTranslate(0, -600)
.ExpectChildMeasure("d", expectedInput: new Size(500, 400), returns: new FullRender(200, 400)) .ExpectChildMeasure("d", expectedInput: new Size(500, 400), returns: new FullRender(200, 400))
.ExpectCanvasTranslate(0, 600) .ExpectCanvasTranslate(0, 600)

Просмотреть файл

@ -0,0 +1,43 @@
using System;
using QuestPDF.Infrastructure;
using SkiaSharp;
namespace QuestPDF.UnitTests.TestEngine
{
internal class CanvasMock : ICanvas
{
public Action<Position> TranslateFunc { get; set; }
public Action<SKImage, Position, Size> DrawImageFunc { get; set; }
public Action<string, Position, TextStyle> DrawTextFunc { get; set; }
public Action<Position, Size, string> DrawRectFunc { get; set; }
public void Translate(Position vector) => TranslateFunc(vector);
public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color);
public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style);
public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
public void DrawExternalLink(string url, Size size)
{
throw new NotImplementedException();
}
public void DrawLocationLink(string locationName, Size size)
{
throw new NotImplementedException();
}
public void DrawLocation(string locationName)
{
throw new NotImplementedException();
}
public void DrawLink(string url, Size size)
{
throw new NotImplementedException();
}
public Size MeasureText(string text, TextStyle style)
{
return new Size(text.Length * style.Size, style.Size);
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests.TestEngine
{
internal class ElementMock : Element
{
public string Id { get; set; }
public Func<Size, ISpacePlan> MeasureFunc { get; set; }
public Action<Size> DrawFunc { get; set; }
internal override ISpacePlan Measure(Size availableSpace) => MeasureFunc(availableSpace);
internal override void Draw(ICanvas canvas, Size availableSpace) => DrawFunc(availableSpace);
}
}

Просмотреть файл

@ -0,0 +1,7 @@
namespace QuestPDF.UnitTests.TestEngine
{
public abstract class OperationBase
{
}
}

Просмотреть файл

@ -0,0 +1,16 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests.TestEngine.Operations
{
internal class CanvasDrawImageOperationBase : OperationBase
{
public Position Position { get; }
public Size Size { get; }
public CanvasDrawImageOperationBase(Position position, Size size)
{
Position = position;
Size = size;
}
}
}

Просмотреть файл

@ -0,0 +1,18 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests.TestEngine.Operations
{
internal class CanvasDrawRectangleOperationBase : OperationBase
{
public Position Position { get; }
public Size Size { get; }
public string Color { get; }
public CanvasDrawRectangleOperationBase(Position position, Size size, string color)
{
Position = position;
Size = size;
Color = color;
}
}
}

Просмотреть файл

@ -0,0 +1,18 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests.TestEngine.Operations
{
internal class CanvasDrawTextOperationBase : OperationBase
{
public string Text { get; }
public Position Position { get; }
public TextStyle Style { get; }
public CanvasDrawTextOperationBase(string text, Position position, TextStyle style)
{
Text = text;
Position = position;
Style = style;
}
}
}

Просмотреть файл

@ -0,0 +1,14 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests.TestEngine.Operations
{
internal class CanvasTranslateOperationBase : OperationBase
{
public Position Position { get; }
public CanvasTranslateOperationBase(Position position)
{
Position = position;
}
}
}

Просмотреть файл

@ -0,0 +1,16 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests.TestEngine.Operations
{
public class ChildDrawOperationBase : OperationBase
{
public string ChildId { get; }
public Size Input { get; }
public ChildDrawOperationBase(string childId, Size input)
{
ChildId = childId;
Input = input;
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests.TestEngine.Operations
{
internal class ChildMeasureOperationBase : OperationBase
{
public string ChildId { get; }
public Size Input { get; }
public ISpacePlan Output { get; }
public ChildMeasureOperationBase(string childId, Size input, ISpacePlan output)
{
ChildId = childId;
Input = input;
Output = output;
}
}
}

Просмотреть файл

@ -0,0 +1,12 @@
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests.TestEngine.Operations
{
public class ElementMeasureOperationBase : OperationBase
{
public ElementMeasureOperationBase(Size input)
{
}
}
}

Просмотреть файл

@ -0,0 +1,31 @@
using FluentAssertions;
using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.UnitTests.TestEngine
{
public static class SingleChildTests
{
internal static void MeasureWithoutChild<T>(this T element) where T : ContainerElement
{
element.Child = null;
element.Measure(Size.Zero).Should().BeEquivalentTo(new FullRender(Size.Zero));
}
internal static void DrawWithoutChild<T>(this T element) where T : ContainerElement
{
// component does not throw an exception when called with null child
Assert.DoesNotThrow(() =>
{
element.Child = null;
// component does not perform any canvas operation when called with null child
TestPlan
.For(x => element)
.DrawElement(new Size(200, 100))
.CheckDrawResult();
});
}
}
}

Просмотреть файл

@ -1,144 +1,20 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json; using System.Text.Json;
using FluentAssertions;
using NUnit.Framework; using NUnit.Framework;
using QuestPDF.Drawing.SpacePlan; using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using SkiaSharp; using QuestPDF.UnitTests.TestEngine.Operations;
namespace QuestPDF.UnitTests.MeasureTest namespace QuestPDF.UnitTests.TestEngine
{ {
public abstract class Operation
{
}
public class ElementMeasureOperation : Operation
{
public ElementMeasureOperation(Size input)
{
}
}
internal class ChildMeasureOperation : Operation
{
public string ChildId { get; }
public Size Input { get; }
public ISpacePlan Output { get; }
public ChildMeasureOperation(string childId, Size input, ISpacePlan output)
{
ChildId = childId;
Input = input;
Output = output;
}
}
public class ChildDrawOperation : Operation
{
public string ChildId { get; }
public Size Input { get; }
public ChildDrawOperation(string childId, Size input)
{
ChildId = childId;
Input = input;
}
}
internal class CanvasTranslateOperation : Operation
{
public Position Position { get; }
public CanvasTranslateOperation(Position position)
{
Position = position;
}
}
internal class CanvasDrawRectangleOperation : Operation
{
public Position Position { get; }
public Size Size { get; }
public string Color { get; }
public CanvasDrawRectangleOperation(Position position, Size size, string color)
{
Position = position;
Size = size;
Color = color;
}
}
internal class CanvasDrawTextOperation : Operation
{
public string Text { get; }
public Position Position { get; }
public TextStyle Style { get; }
public CanvasDrawTextOperation(string text, Position position, TextStyle style)
{
Text = text;
Position = position;
Style = style;
}
}
internal class CanvasDrawImageOperation : Operation
{
public Position Position { get; }
public Size Size { get; }
public CanvasDrawImageOperation(Position position, Size size)
{
Position = position;
Size = size;
}
}
internal class ElementMock : Element
{
public string Id { get; set; }
public Func<Size, ISpacePlan> MeasureFunc { get; set; }
public Action<Size> DrawFunc { get; set; }
internal override ISpacePlan Measure(Size availableSpace) => MeasureFunc(availableSpace);
internal override void Draw(ICanvas canvas, Size availableSpace) => DrawFunc(availableSpace);
}
internal class CanvasMock : ICanvas
{
public Action<Position> TranslateFunc { get; set; }
public Action<SKImage, Position, Size> DrawImageFunc { get; set; }
public Action<string, Position, TextStyle> DrawTextFunc { get; set; }
public Action<Position, Size, string> DrawRectFunc { get; set; }
public void Translate(Position vector) => TranslateFunc(vector);
public void DrawRectangle(Position vector, Size size, string color) => DrawRectFunc(vector, size, color);
public void DrawText(string text, Position position, TextStyle style) => DrawTextFunc(text, position, style);
public void DrawImage(SKImage image, Position position, Size size) => DrawImageFunc(image, position, size);
public void DrawLink(string url, Size size)
{
throw new NotImplementedException();
}
public Size MeasureText(string text, TextStyle style)
{
return new Size(text.Length * style.Size, style.Size);
}
}
internal class TestPlan internal class TestPlan
{ {
private Element Element { get; set; } private Element Element { get; set; }
private ICanvas Canvas { get; } private ICanvas Canvas { get; }
private Size OperationInput { get; set; } private Size OperationInput { get; set; }
private Queue<Operation> Operations { get; } = new Queue<Operation>(); private Queue<OperationBase> Operations { get; } = new Queue<OperationBase>();
public TestPlan() public TestPlan()
{ {
@ -153,7 +29,7 @@ namespace QuestPDF.UnitTests.MeasureTest
return plan; return plan;
} }
private T GetExpected<T>() where T : Operation private T GetExpected<T>() where T : OperationBase
{ {
if (Operations.TryDequeue(out var value) && value is T result) if (Operations.TryDequeue(out var value) && value is T result)
return result; return result;
@ -169,7 +45,7 @@ namespace QuestPDF.UnitTests.MeasureTest
{ {
TranslateFunc = position => TranslateFunc = position =>
{ {
var expected = GetExpected<CanvasTranslateOperation>(); var expected = GetExpected<CanvasTranslateOperationBase>();
Assert.AreEqual(expected.Position.X, position.X); Assert.AreEqual(expected.Position.X, position.X);
Assert.AreEqual(expected.Position.Y, position.Y); Assert.AreEqual(expected.Position.Y, position.Y);
@ -178,7 +54,7 @@ namespace QuestPDF.UnitTests.MeasureTest
}, },
DrawRectFunc = (position, size, color) => DrawRectFunc = (position, size, color) =>
{ {
var expected = GetExpected<CanvasDrawRectangleOperation>(); var expected = GetExpected<CanvasDrawRectangleOperationBase>();
Assert.AreEqual(expected.Position.X, position.X); Assert.AreEqual(expected.Position.X, position.X);
Assert.AreEqual(expected.Position.Y, position.Y); Assert.AreEqual(expected.Position.Y, position.Y);
@ -194,7 +70,7 @@ namespace QuestPDF.UnitTests.MeasureTest
}, },
DrawTextFunc = (text, position, style) => DrawTextFunc = (text, position, style) =>
{ {
var expected = GetExpected<CanvasDrawTextOperation>(); var expected = GetExpected<CanvasDrawTextOperationBase>();
Assert.AreEqual(expected.Text, text); Assert.AreEqual(expected.Text, text);
@ -211,7 +87,7 @@ namespace QuestPDF.UnitTests.MeasureTest
}, },
DrawImageFunc = (image, position, size) => DrawImageFunc = (image, position, size) =>
{ {
var expected = GetExpected<CanvasDrawImageOperation>(); var expected = GetExpected<CanvasDrawImageOperationBase>();
Assert.AreEqual(expected.Position.X, position.X); Assert.AreEqual(expected.Position.X, position.X);
Assert.AreEqual(expected.Position.Y, position.Y); Assert.AreEqual(expected.Position.Y, position.Y);
@ -232,7 +108,7 @@ namespace QuestPDF.UnitTests.MeasureTest
Id = id, Id = id,
MeasureFunc = availableSpace => MeasureFunc = availableSpace =>
{ {
var expected = GetExpected<ChildMeasureOperation>(); var expected = GetExpected<ChildMeasureOperationBase>();
Assert.AreEqual(expected.ChildId, id); Assert.AreEqual(expected.ChildId, id);
@ -246,7 +122,7 @@ namespace QuestPDF.UnitTests.MeasureTest
}, },
DrawFunc = availableSpace => DrawFunc = availableSpace =>
{ {
var expected = GetExpected<ChildDrawOperation>(); var expected = GetExpected<ChildDrawOperationBase>();
Assert.AreEqual(expected.ChildId, id); Assert.AreEqual(expected.ChildId, id);
@ -271,45 +147,45 @@ namespace QuestPDF.UnitTests.MeasureTest
return this; return this;
} }
private TestPlan AddOperation(Operation operation) private TestPlan AddOperation(OperationBase operationBase)
{ {
Operations.Enqueue(operation); Operations.Enqueue(operationBase);
return this; return this;
} }
public TestPlan ExpectChildMeasure(string child, Size expectedInput, ISpacePlan returns) public TestPlan ExpectChildMeasure(string child, Size expectedInput, ISpacePlan returns)
{ {
return AddOperation(new ChildMeasureOperation(child, expectedInput, returns)); return AddOperation(new ChildMeasureOperationBase(child, expectedInput, returns));
} }
public TestPlan ExpectChildDraw(string child, Size expectedInput) public TestPlan ExpectChildDraw(string child, Size expectedInput)
{ {
return AddOperation(new ChildDrawOperation(child, expectedInput)); return AddOperation(new ChildDrawOperationBase(child, expectedInput));
} }
public TestPlan ExpectCanvasTranslate(Position position) public TestPlan ExpectCanvasTranslate(Position position)
{ {
return AddOperation(new CanvasTranslateOperation(position)); return AddOperation(new CanvasTranslateOperationBase(position));
} }
public TestPlan ExpectCanvasTranslate(float left, float top) public TestPlan ExpectCanvasTranslate(float left, float top)
{ {
return AddOperation(new CanvasTranslateOperation(new Position(left, top))); return AddOperation(new CanvasTranslateOperationBase(new Position(left, top)));
} }
public TestPlan ExpectCanvasDrawRectangle(Position position, Size size, string color) public TestPlan ExpectCanvasDrawRectangle(Position position, Size size, string color)
{ {
return AddOperation(new CanvasDrawRectangleOperation(position, size, color)); return AddOperation(new CanvasDrawRectangleOperationBase(position, size, color));
} }
public TestPlan ExpectCanvasDrawText(string text, Position position, TextStyle style) public TestPlan ExpectCanvasDrawText(string text, Position position, TextStyle style)
{ {
return AddOperation(new CanvasDrawTextOperation(text, position, style)); return AddOperation(new CanvasDrawTextOperationBase(text, position, style));
} }
public TestPlan ExpectCanvasDrawImage(Position position, Size size) public TestPlan ExpectCanvasDrawImage(Position position, Size size)
{ {
return AddOperation(new CanvasDrawImageOperation(position, size)); return AddOperation(new CanvasDrawImageOperationBase(position, size));
} }
public TestPlan CheckMeasureResult(ISpacePlan expected) public TestPlan CheckMeasureResult(ISpacePlan expected)
@ -336,70 +212,4 @@ namespace QuestPDF.UnitTests.MeasureTest
return this; return this;
} }
} }
public static class SingleChildTests
{
internal static void MeasureWithoutChild<T>(this T element) where T : ContainerElement
{
element.Child = null;
element.Measure(Size.Zero).Should().BeEquivalentTo(new FullRender(Size.Zero));
}
internal static void DrawWithoutChild<T>(this T element) where T : ContainerElement
{
// component does not throw an exception when called with null child
Assert.DoesNotThrow(() =>
{
element.Child = null;
// component does not perform any canvas operation when called with null child
TestPlan
.For(x => element)
.DrawElement(new Size(200, 100))
.CheckDrawResult();
});
}
}
[TestFixture]
public class LetsTest
{
[Test]
public void TestExample()
{
TestPlan
.For(x => new Padding
{
Top = 5,
Right = 10,
Bottom = 15,
Left = 20,
Child = x.CreateChild("child")
})
.MeasureElement(new Size(200, 100))
.ExpectChildMeasure("child", expectedInput: new Size(170, 80), returns: new FullRender(new Size(100, 50)))
.CheckMeasureResult(new FullRender(130, 70));
}
[Test]
public void TestExample2()
{
TestPlan
.For(x => new Padding
{
Top = 5,
Right = 10,
Bottom = 15,
Left = 20,
Child = x.CreateChild("child")
})
.DrawElement(new Size(200, 100))
.ExpectCanvasTranslate(new Position(20, 5))
.ExpectChildDraw("child", expectedInput: new Size(170, 80))
.ExpectCanvasTranslate(new Position(-20, -5))
.CheckDrawResult();
}
}
} }

Просмотреть файл

@ -41,9 +41,19 @@ namespace QuestPDF.Drawing
SkiaCanvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height)); SkiaCanvas.DrawImage(image, new SKRect(vector.X, vector.Y, size.Width, size.Height));
} }
public void DrawLink(string url, Size size) public void DrawExternalLink(string url, Size size)
{ {
SkiaCanvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url); SkiaCanvas.DrawUrlAnnotation(new SKRect(0, 0, size.Width, size.Height), url);
} }
public void DrawLocationLink(string locationName, Size size)
{
SkiaCanvas.DrawLinkDestinationAnnotation(new SKRect(0, 0, size.Width, size.Height), locationName);
}
public void DrawLocation(string locationName)
{
SkiaCanvas.DrawNamedDestinationAnnotation(new SKPoint(0, 0), locationName);
}
} }
} }

Просмотреть файл

@ -30,13 +30,15 @@ namespace QuestPDF.Drawing
static SKPaint Convert(TextStyle style) static SKPaint Convert(TextStyle style)
{ {
var slant = style.IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
return new SKPaint return new SKPaint
{ {
Color = SKColor.Parse(style.Color), Color = SKColor.Parse(style.Color),
Typeface = Fonts.GetOrAdd(style.FontType, SKTypeface.FromFamilyName), Typeface = SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant),
TextSize = style.Size, TextSize = style.Size,
IsLinearText = true, TextEncoding = SKTextEncoding.Utf32,
TextAlign = style.Alignment switch TextAlign = style.Alignment switch
{ {
HorizontalAlignment.Left => SKTextAlign.Left, HorizontalAlignment.Left => SKTextAlign.Left,

Просмотреть файл

@ -12,7 +12,6 @@ namespace QuestPDF.Drawing
static class DocumentGenerator static class DocumentGenerator
{ {
const int DocumentLayoutExceptionThreshold = 250; const int DocumentLayoutExceptionThreshold = 250;
private static readonly Watermark Watermark = new Watermark();
internal static void Generate(Stream stream, IDocument document) internal static void Generate(Stream stream, IDocument document)
{ {

Просмотреть файл

@ -0,0 +1,21 @@
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class ExternalLink : ContainerElement
{
public string Url { get; set; } = "https://www.questpdf.com";
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var targetSize = Child?.Measure(availableSpace) as Size;
if (targetSize == null)
return;
canvas.DrawExternalLink(Url, targetSize);
Child?.Draw(canvas, availableSpace);
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class InternalLink : ContainerElement
{
public string LocationName { get; set; }
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var targetSize = Child?.Measure(availableSpace) as Size;
if (targetSize == null)
return;
canvas.DrawLocationLink(LocationName, targetSize);
Child?.Draw(canvas, availableSpace);
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
using System;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class InternalLocation : ContainerElement
{
public string LocationName { get; set; }
internal override void Draw(ICanvas canvas, Size availableSpace)
{
canvas.DrawLocation(LocationName);
Child?.Draw(canvas, availableSpace);
}
}
}

Просмотреть файл

@ -47,10 +47,8 @@ namespace QuestPDF.Elements
var size = space as Size; var size = space as Size;
if (size.Height < Size.Epsilon) if (size.Height > Size.Epsilon)
continue; heightOnCurrentPage += size.Height + Spacing;
heightOnCurrentPage += size.Height + Spacing;
if (space is PartialRender) if (space is PartialRender)
{ {
@ -81,18 +79,13 @@ namespace QuestPDF.Elements
break; break;
var size = space as Size; var size = space as Size;
if (size.Height < Size.Epsilon)
{
ChildrenQueue.Dequeue();
continue;
}
canvas.Translate(new Position(0, topOffset)); canvas.Translate(new Position(0, topOffset));
child.Draw(canvas, new Size(availableSpace.Width, size.Height)); child.Draw(canvas, new Size(availableSpace.Width, size.Height));
canvas.Translate(new Position(0, -topOffset)); canvas.Translate(new Position(0, -topOffset));
topOffset += size.Height + Spacing; if (size.Height > Size.Epsilon)
topOffset += size.Height + Spacing;
if (space is PartialRender) if (space is PartialRender)
break; break;

Просмотреть файл

@ -1,62 +0,0 @@
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements
{
internal class Watermark : Element
{
private Position Offset { get; set; } = new Position(36, 36);
private const float ImageHeight = 28;
private const string TargetUrl = "https://www.questpdf.com";
private Image Image { get; set; }
private static readonly byte[] ImageData;
static Watermark()
{
ImageData = Helpers.Helpers.LoadEmbeddedResource("QuestPDF.Resources.Watermark.png");
}
public Watermark()
{
Image = new Image()
{
Data = ImageData
};
}
internal void AdjustPosition(Element? element)
{
while (element != null)
{
if (element is Padding padding)
{
if (padding.Left > 0 && padding.Bottom > 0)
Offset = new Position(padding.Left, padding.Bottom);
return;
}
element = (element as ContainerElement)?.Child;
}
}
internal override ISpacePlan Measure(Size availableSpace)
{
return Image.Measure(availableSpace);
}
internal override void Draw(ICanvas canvas, Size availableSpace)
{
var offset = new Position(Offset.X, availableSpace.Height - Offset.Y - ImageHeight);
canvas.Translate(offset);
availableSpace = new Size(availableSpace.Width, ImageHeight);
var targetSize = Image.Measure(availableSpace) as FullRender;
Image.Draw(canvas, targetSize);
canvas.DrawLink(TargetUrl, targetSize);
canvas.Translate(offset.Reverse());
}
}
}

Просмотреть файл

@ -66,6 +66,14 @@ namespace QuestPDF.Fluent
}); });
} }
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) public static void PageNumber(this IContainer element, string textFormat = "{number}", TextStyle? style = null)
{ {
element.Element(new PageNumber element.Element(new PageNumber
@ -127,11 +135,27 @@ namespace QuestPDF.Fluent
return element.Element(new Container()); return element.Element(new Container());
} }
private static void DynamicImage(this IContainer element, Func<Size, byte[]> handler) public static IContainer ExternalLink(this IContainer element, string url)
{ {
element.Element(new DynamicImage() return element.Element(new ExternalLink
{ {
Source = handler Url = url
});
}
public static IContainer Location(this IContainer element, string locationName)
{
return element.Element(new InternalLocation
{
LocationName = locationName
});
}
public static IContainer InternalLink(this IContainer element, string locationName)
{
return element.Element(new InternalLink
{
LocationName = locationName
}); });
} }
} }

Просмотреть файл

@ -31,6 +31,13 @@ namespace QuestPDF.Fluent
return style.Mutate(x => x.LineHeight = value); return style.Mutate(x => x.LineHeight = value);
} }
public static TextStyle Italic(this TextStyle style, bool value = true)
{
return style.Mutate(x => x.IsItalic = value);
}
#region Alignmnet
public static TextStyle Alignment(this TextStyle style, HorizontalAlignment value) public static TextStyle Alignment(this TextStyle style, HorizontalAlignment value)
{ {
return style.Mutate(x => x.Alignment = value); return style.Mutate(x => x.Alignment = value);
@ -38,17 +45,78 @@ namespace QuestPDF.Fluent
public static TextStyle AlignLeft(this TextStyle style) public static TextStyle AlignLeft(this TextStyle style)
{ {
return style.Mutate(x => x.Alignment = HorizontalAlignment.Left); return style.Alignment(HorizontalAlignment.Left);
} }
public static TextStyle AlignCenter(this TextStyle style) public static TextStyle AlignCenter(this TextStyle style)
{ {
return style.Mutate(x => x.Alignment = HorizontalAlignment.Center); return style.Alignment(HorizontalAlignment.Center);
} }
public static TextStyle AlignRight(this TextStyle style) public static TextStyle AlignRight(this TextStyle style)
{ {
return style.Mutate(x => x.Alignment = HorizontalAlignment.Right); return style.Alignment(HorizontalAlignment.Right);
} }
#endregion
#region Weight
public static TextStyle Weight(this TextStyle style, FontWeight weight)
{
return style.Mutate(x => x.FontWeight = weight);
}
public static TextStyle Thin(this TextStyle style)
{
return style.Weight(FontWeight.Thin);
}
public static TextStyle ExtraLight(this TextStyle style)
{
return style.Weight(FontWeight.ExtraLight);
}
public static TextStyle Light(this TextStyle style)
{
return style.Weight(FontWeight.Light);
}
public static TextStyle NormalWeight(this TextStyle style)
{
return style.Weight(FontWeight.Normal);
}
public static TextStyle Medium(this TextStyle style)
{
return style.Weight(FontWeight.Medium);
}
public static TextStyle SemiBold(this TextStyle style)
{
return style.Weight(FontWeight.SemiBold);
}
public static TextStyle Bold(this TextStyle style)
{
return style.Weight(FontWeight.Bold);
}
public static TextStyle ExtraBold(this TextStyle style)
{
return style.Weight(FontWeight.ExtraBold);
}
public static TextStyle Black(this TextStyle style)
{
return style.Weight(FontWeight.Black);
}
public static TextStyle ExtraBlack(this TextStyle style)
{
return style.Weight(FontWeight.ExtraBlack);
}
#endregion
} }
} }

Просмотреть файл

@ -0,0 +1,16 @@
namespace QuestPDF.Infrastructure
{
public enum FontWeight
{
Thin = 100,
ExtraLight = 200,
Light = 300,
Normal = 400,
Medium = 500,
SemiBold = 600,
Bold = 700,
ExtraBold = 800,
Black = 900,
ExtraBlack = 1000
}
}

Просмотреть файл

@ -10,6 +10,8 @@ namespace QuestPDF.Infrastructure
void DrawText(string text, Position position, TextStyle style); void DrawText(string text, Position position, TextStyle style);
void DrawImage(SKImage image, Position position, Size size); void DrawImage(SKImage image, Position position, Size size);
void DrawLink(string url, Size size); void DrawExternalLink(string url, Size size);
void DrawLocationLink(string locationName, Size size);
void DrawLocation(string locationName);
} }
} }

Просмотреть файл

@ -2,17 +2,19 @@
{ {
public class TextStyle public class TextStyle
{ {
public string Color { get; set; } = "#000000"; internal string Color { get; set; } = "#000000";
public string FontType { get; set; } = "Helvetica"; internal string FontType { get; set; } = "Helvetica";
public float Size { get; set; } = 12; internal float Size { get; set; } = 12;
public float LineHeight { get; set; } = 1.2f; internal float LineHeight { get; set; } = 1.2f;
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
internal FontWeight FontWeight { get; set; } = FontWeight.Normal;
internal bool IsItalic { get; set; } = false;
public static TextStyle Default => new TextStyle(); public static TextStyle Default => new TextStyle();
public override string ToString() public override string ToString()
{ {
return $"{Color}|{FontType}|{Size}|{LineHeight}|{Alignment}"; return $"{Color}|{FontType}|{Size}|{LineHeight}|{Alignment}|{FontWeight}|{IsItalic}";
} }
} }
} }

Просмотреть файл

@ -4,9 +4,9 @@
<Authors>MarcinZiabek</Authors> <Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company> <Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId> <PackageId>QuestPDF</PackageId>
<Version>2021.1.0</Version> <Version>2021.2.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> <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>The library is open-source and totally free now. Removed watermak.</PackageReleaseNotes> <PackageReleaseNotes>2021.2.0 Internal links, external links, dynamic images, font weights</PackageReleaseNotes>
<LangVersion>8</LangVersion> <LangVersion>8</LangVersion>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageIcon>Logo.png</PackageIcon> <PackageIcon>Logo.png</PackageIcon>