Compare commits

..

5 Commits

Author SHA1 Message Date
Marcin Ziąbek 70e5783f0e Updated report sample to follow new text style approach 2021-10-07 23:23:07 +02:00
Marcin Ziąbek e8b7f38187 Fixed setting text style in the text-block-elements 2021-10-07 23:10:12 +02:00
Marcin Ziąbek c2ba2addfc Fixed handling global text style when setting block-wide style 2021-10-07 22:56:51 +02:00
Marcin Ziąbek e379502a64 Implemented prototype of global, document-wide text style 2021-10-07 22:38:41 +02:00
Marcin Ziąbek 17e2727833 Added missing option to style a line 2021-10-01 01:44:53 +02:00
19 changed files with 252 additions and 108 deletions
+86
View File
@@ -11,6 +11,92 @@ namespace QuestPDF.Examples
{
public class TextExamples
{
[Test]
public void SimpleTextBlock()
{
RenderingTest
.Create()
.PageSize(500, 300)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.Span("This is a normal text, followed by an ");
text.Span("underlined red text.", TextStyle.Default.Size(20).Color(Colors.Red.Medium).Underline());
});
});
}
[Test]
public void ParagraphSpacing()
{
RenderingTest
.Create()
.PageSize(500, 300)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.ParagraphSpacing(10);
foreach (var i in Enumerable.Range(1, 3))
{
text.Span($"Paragraph {i}: ", TextStyle.Default.SemiBold());
text.Line(Placeholders.Paragraph());
}
});
});
}
[Test]
public void CustomElement()
{
RenderingTest
.Create()
.PageSize(500, 200)
.FileName()
.ProduceImages()
.ShowResults()
.Render(container =>
{
container
.Padding(5)
.Box()
.Border(1)
.Padding(10)
.Text(text =>
{
text.DefaultTextStyle(TextStyle.Default.Size(20));
text.Span("This is a random image aligned to the baseline: ");
text.Element()
.PaddingBottom(-6)
.Height(24)
.Width(48)
.Image(Placeholders.Image);
text.Span(".");
});
});
}
[Test]
public void TextElements()
{
@@ -1,5 +1,6 @@
using System;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.ReportSample.Layouts
@@ -24,7 +25,7 @@ namespace QuestPDF.ReportSample.Layouts
{
container
.AspectRatio(AspectRatio)
.Background("#EEEEEE")
.Background(Colors.Grey.Lighten3)
.Image(Source(Size.Zero));
}
}
@@ -48,13 +48,13 @@ namespace QuestPDF.ReportSample.Layouts
{
grid.Columns(6);
grid.Item().LabelCell().Text("Date", Typography.Normal);
grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty, Typography.Normal);
grid.Item().LabelCell().Text("Location", Typography.Normal);
grid.Item(2).ValueCell().Text(Model.Location.Format(), Typography.Normal);
grid.Item().LabelCell().Text("Date");
grid.Item(2).ValueCell().Text(Model.Date?.ToString("g") ?? string.Empty);
grid.Item().LabelCell().Text("Location");
grid.Item(2).ValueCell().Text(Model.Location.Format());
grid.Item().LabelCell().Text("Comments", Typography.Normal);
grid.Item(5).ValueCell().Text(Model.Comments, Typography.Normal);
grid.Item().LabelCell().Text("Comments");
grid.Item(5).ValueCell().Text(Model.Comments);
});
}
}
@@ -30,11 +30,11 @@ namespace QuestPDF.ReportSample.Layouts
{
stack.Item().EnsureSpace(25).Row(row =>
{
row.ConstantColumn(150).LabelCell().Text(part.Label, Typography.Normal);
row.ConstantColumn(150).LabelCell().Text(part.Label);
var frame = row.RelativeColumn().ValueCell();
if (part is ReportSectionText text)
frame.ShowEntire().Text(text.Text, Typography.Normal);
frame.ShowEntire().Text(text.Text);
if (part is ReportSectionMap map)
frame.Element(x => MapElement(x, map));
@@ -51,7 +51,7 @@ namespace QuestPDF.ReportSample.Layouts
{
if (model.ImageSource == null || model.Location == null)
{
container.Text("No location provided", Typography.Normal);
container.Text("No location provided");
return;
}
@@ -60,7 +60,7 @@ namespace QuestPDF.ReportSample.Layouts
stack.Spacing(5);
stack.Item().MaxWidth(250).AspectRatio(4 / 3f).Image(Placeholders.Image);
stack.Item().Text(model.Location.Format(), Typography.Normal);
stack.Item().Text(model.Location.Format());
});
}
@@ -68,7 +68,7 @@ namespace QuestPDF.ReportSample.Layouts
{
if (model.Photos.Count == 0)
{
container.Text("No photos", Typography.Normal);
container.Text("No photos");
return;
}
@@ -25,6 +25,7 @@ namespace QuestPDF.ReportSample.Layouts
public void Compose(IDocumentContainer container)
{
container
.DefaultTextStyle(Typography.Normal)
.Page(page =>
{
page.MarginVertical(40);
@@ -37,8 +38,6 @@ namespace QuestPDF.ReportSample.Layouts
page.Footer().AlignCenter().Text(text =>
{
text.DefaultTextStyle(Typography.Normal);
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
@@ -69,8 +68,8 @@ namespace QuestPDF.ReportSample.Layouts
{
grid.Item().Text(text =>
{
text.Span($"{field.Label}: ", Typography.Normal.SemiBold());
text.Span(field.Value, Typography.Normal);
text.Span($"{field.Label}: ", TextStyle.Default.SemiBold());
text.Span(field.Value);
});
}
});
@@ -41,9 +41,9 @@ namespace QuestPDF.ReportSample.Layouts
.InternalLink(locationName)
.Row(row =>
{
row.ConstantColumn(25).Text($"{number}.", Typography.Normal);
row.RelativeColumn().Text(locationName, Typography.Normal);
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName, Typography.Normal));
row.ConstantColumn(25).Text($"{number}.");
row.RelativeColumn().Text(locationName);
row.ConstantColumn(150).AlignRight().Text(text => text.PageNumberOfLocation(locationName));
});
}
}
+1 -1
View File
@@ -8,6 +8,6 @@ namespace QuestPDF.ReportSample
{
public static TextStyle Title => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Darken3).Size(26).Black();
public static TextStyle Headline => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Blue.Medium).Size(16).SemiBold();
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Calibri).Color(Colors.Black).Size(11).LineHeight(1.1f);
public static TextStyle Normal => TextStyle.Default.FontType(Fonts.Verdana).Color(Colors.Black).Size(10).LineHeight(1.2f);
}
}
+1
View File
@@ -9,6 +9,7 @@ namespace QuestPDF.Drawing
{
internal class DocumentContainer : IDocumentContainer
{
internal TextStyle DefaultTextStyle { get; set; } = TextStyle.Default;
internal List<IComponent> Pages { get; set; } = new List<IComponent>();
internal Container Compose()
+26
View File
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using QuestPDF.Drawing.Exceptions;
using QuestPDF.Drawing.SpacePlan;
using QuestPDF.Elements;
using QuestPDF.Elements.Text;
using QuestPDF.Elements.Text.Items;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing
@@ -36,6 +39,7 @@ namespace QuestPDF.Drawing
var metadata = document.GetMetadata();
var pageContext = new PageContext();
ApplyDefaultTextStyle(content, container.DefaultTextStyle);
RenderPass(pageContext, new FreeCanvas(), content, metadata);
RenderPass(pageContext, canvas, content, metadata);
}
@@ -93,5 +97,27 @@ namespace QuestPDF.Drawing
throw new DocumentLayoutException("Composed layout generates infinite document.");
}
}
private static void ApplyDefaultTextStyle(Element content, TextStyle documentDefaultTextStyle)
{
documentDefaultTextStyle.ApplyGlobalStyle(TextStyle.LibraryDefault);
content.HandleVisitor(element =>
{
var text = element as TextBlock;
if (text == null)
return;
foreach (var child in text.Children)
{
if (child is TextBlockSpan textSpan)
textSpan.Style.ApplyGlobalStyle(documentDefaultTextStyle);
if (child is TextBlockElement textElement)
ApplyDefaultTextStyle(textElement.Element, documentDefaultTextStyle);
}
});
}
}
}
+5 -5
View File
@@ -34,7 +34,7 @@ namespace QuestPDF.Drawing
internal static SKPaint ToPaint(this TextStyle style)
{
return Paints.GetOrAdd(style.ToString(), key => Convert(style));
return Paints.GetOrAdd(style.Key, key => Convert(style));
static SKPaint Convert(TextStyle style)
{
@@ -42,7 +42,7 @@ namespace QuestPDF.Drawing
{
Color = SKColor.Parse(style.Color),
Typeface = GetTypeface(style),
TextSize = style.Size,
TextSize = (style.Size ?? 12),
TextEncoding = SKTextEncoding.Utf32
};
}
@@ -52,16 +52,16 @@ namespace QuestPDF.Drawing
if (Typefaces.TryGetValue(style.FontType, out var result))
return result;
var slant = style.IsItalic ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
return SKTypeface.FromFamilyName(style.FontType, (int)style.FontWeight, (int)SKFontStyleWidth.Normal, slant)
return SKTypeface.FromFamilyName(style.FontType, (int)(style.FontWeight ?? FontWeight.Normal), (int)SKFontStyleWidth.Normal, slant)
?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
}
}
internal static SKFontMetrics ToFontMetrics(this TextStyle style)
{
return FontMetrics.GetOrAdd(style.ToString(), key => style.ToPaint().FontMetrics);
return FontMetrics.GetOrAdd(style.Key, key => style.ToPaint().FontMetrics);
}
}
}
@@ -1,4 +1,5 @@
using QuestPDF.Elements.Text.Calculation;
using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
@@ -3,33 +3,22 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockExternalLink : ITextBlockItem
internal class TextBlockExternalLink : TextBlockSpan
{
public TextStyle Style { get; set; } = new TextStyle();
public string Text { get; set; }
public string Url { get; set; }
public TextMeasurementResult? Measure(TextMeasurementRequest request)
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
{
return GetItem().MeasureWithoutCache(request);
return MeasureWithoutCache(request);
}
public void Draw(TextDrawingRequest request)
public override void Draw(TextDrawingRequest request)
{
request.Canvas.Translate(new Position(0, request.TotalAscent));
request.Canvas.DrawExternalLink(Url, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.Translate(new Position(0, -request.TotalAscent));
GetItem().Draw(request);
}
private TextBlockSpan GetItem()
{
return new TextBlockSpan
{
Style = Style,
Text = Text
};
base.Draw(request);
}
}
}
@@ -3,33 +3,22 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockInternalLink : ITextBlockItem
internal class TextBlockInternalLink : TextBlockSpan
{
public TextStyle Style { get; set; } = new TextStyle();
public string Text { get; set; }
public string LocationName { get; set; }
public TextMeasurementResult? Measure(TextMeasurementRequest request)
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
{
return GetItem().MeasureWithoutCache(request);
return MeasureWithoutCache(request);
}
public void Draw(TextDrawingRequest request)
public override void Draw(TextDrawingRequest request)
{
request.Canvas.Translate(new Position(0, request.TotalAscent));
request.Canvas.DrawLocationLink(LocationName, new Size(request.TextSize.Width, request.TextSize.Height));
request.Canvas.Translate(new Position(0, -request.TotalAscent));
GetItem().Draw(request);
}
private TextBlockSpan GetItem()
{
return new TextBlockSpan
{
Style = Style,
Text = Text
};
base.Draw(request);
}
}
}
@@ -3,34 +3,31 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text.Items
{
internal class TextBlockPageNumber : ITextBlockItem
internal class TextBlockPageNumber : TextBlockSpan
{
public TextStyle Style { get; set; } = new TextStyle();
public string SlotName { get; set; }
public TextMeasurementResult? Measure(TextMeasurementRequest request)
public override TextMeasurementResult? Measure(TextMeasurementRequest request)
{
return GetItem(request.PageContext).MeasureWithoutCache(request);
SetPageNumber(request.PageContext);
return MeasureWithoutCache(request);
}
public void Draw(TextDrawingRequest request)
public override void Draw(TextDrawingRequest request)
{
GetItem(request.PageContext).Draw(request);
SetPageNumber(request.PageContext);
base.Draw(request);
}
private TextBlockSpan GetItem(IPageContext context)
private void SetPageNumber(IPageContext context)
{
var pageNumberPlaceholder = 123;
var pageNumber = context.GetRegisteredLocations().Contains(SlotName)
? context.GetLocationPage(SlotName)
: pageNumberPlaceholder;
return new TextBlockSpan
{
Style = Style,
Text = pageNumber.ToString()
};
Text = pageNumber.ToString();
}
}
}
@@ -14,7 +14,7 @@ namespace QuestPDF.Elements.Text.Items
private Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?> MeasureCache =
new Dictionary<(int startIndex, float availableWidth), TextMeasurementResult?>();
public TextMeasurementResult? Measure(TextMeasurementRequest request)
public virtual TextMeasurementResult? Measure(TextMeasurementRequest request)
{
var cacheKey = (request.StartIndex, request.AvailableWidth);
@@ -45,7 +45,7 @@ namespace QuestPDF.Elements.Text.Items
{
Width = 0,
LineHeight = Style.LineHeight,
LineHeight = Style.LineHeight ?? 1,
Ascent = fontMetrics.Ascent,
Descent = fontMetrics.Descent
};
@@ -96,7 +96,7 @@ namespace QuestPDF.Elements.Text.Items
Ascent = fontMetrics.Ascent,
Descent = fontMetrics.Descent,
LineHeight = Style.LineHeight,
LineHeight = Style.LineHeight ?? 1,
StartIndex = startIndex,
EndIndex = endIndex,
@@ -105,7 +105,7 @@ namespace QuestPDF.Elements.Text.Items
};
}
public void Draw(TextDrawingRequest request)
public virtual void Draw(TextDrawingRequest request)
{
var fontMetrics = Style.ToFontMetrics();
@@ -115,12 +115,12 @@ namespace QuestPDF.Elements.Text.Items
request.Canvas.DrawText(text, Position.Zero, Style);
// draw underline
if (Style.HasUnderline && fontMetrics.UnderlinePosition.HasValue)
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness.Value);
if ((Style.HasUnderline ?? false) && fontMetrics.UnderlinePosition.HasValue)
DrawLine(fontMetrics.UnderlinePosition.Value, fontMetrics.UnderlineThickness ?? 1);
// draw stroke
if (Style.HasStrikethrough && fontMetrics.StrikeoutPosition.HasValue)
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness.Value);
if ((Style.HasStrikethrough ?? false) && fontMetrics.StrikeoutPosition.HasValue)
DrawLine(fontMetrics.StrikeoutPosition.Value, fontMetrics.StrikeoutThickness ?? 1);
void DrawLine(float offset, float thickness)
{
+6
View File
@@ -94,6 +94,12 @@ namespace QuestPDF.Fluent
public static class PageExtensions
{
public static IDocumentContainer DefaultTextStyle(this IDocumentContainer document, TextStyle textStyle)
{
(document as DocumentContainer).DefaultTextStyle = textStyle;
return document;
}
public static IDocumentContainer Page(this IDocumentContainer document, Action<PageDescriptor> handler)
{
var descriptor = new PageDescriptor();
+19 -11
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Elements.Text;
using QuestPDF.Elements.Text.Items;
@@ -13,7 +14,7 @@ namespace QuestPDF.Fluent
{
private ICollection<TextBlock> TextBlocks { get; } = new List<TextBlock>();
private TextStyle DefaultStyle { get; set; } = TextStyle.Default;
private HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
internal HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
private float Spacing { get; set; } = 0f;
public void DefaultTextStyle(TextStyle style)
@@ -51,7 +52,7 @@ namespace QuestPDF.Fluent
public void Span(string text, TextStyle? style = null)
{
style ??= DefaultStyle;
style ??= TextStyle.Default;
var items = text
.Replace("\r", string.Empty)
@@ -75,6 +76,11 @@ namespace QuestPDF.Fluent
.ForEach(TextBlocks.Add);
}
public void Line(string text, TextStyle? style = null)
{
Span(text + Environment.NewLine, style);
}
public void Line(string text)
{
Span(text + Environment.NewLine);
@@ -87,7 +93,7 @@ namespace QuestPDF.Fluent
private void PageNumber(string slotName, TextStyle? style = null)
{
style ??= DefaultStyle;
style ??= TextStyle.Default;
AddItemToLastTextBlock(new TextBlockPageNumber()
{
@@ -113,7 +119,7 @@ namespace QuestPDF.Fluent
public void InternalLocation(string text, string locationName, TextStyle? style = null)
{
style ??= DefaultStyle;
style ??= TextStyle.Default;
AddItemToLastTextBlock(new TextBlockInternalLink
{
@@ -125,7 +131,7 @@ namespace QuestPDF.Fluent
public void ExternalLocation(string text, string url, TextStyle? style = null)
{
style ??= DefaultStyle;
style ??= TextStyle.Default;
AddItemToLastTextBlock(new TextBlockExternalLink
{
@@ -144,13 +150,16 @@ namespace QuestPDF.Fluent
Element = container
});
return container.Box();
return container.AlignBottom().Box();
}
internal void Compose(IContainer container)
{
TextBlocks.ToList().ForEach(x => x.Alignment = Alignment);
foreach (var textBlockSpan in TextBlocks.SelectMany(x => x.Children).Where(x => x is TextBlockSpan).Cast<TextBlockSpan>())
textBlockSpan.Style.ApplyParentStyle(DefaultStyle);
container.Stack(stack =>
{
stack.Spacing(Spacing);
@@ -165,12 +174,11 @@ namespace QuestPDF.Fluent
{
public static void Text(this IContainer element, Action<TextDescriptor> content)
{
var textBlock = new TextBlock();
if (element is Alignment alignment)
textBlock.Alignment = alignment.Horizontal;
var descriptor = new TextDescriptor();
if (element is Alignment alignment)
descriptor.Alignment = alignment.Horizontal;
content?.Invoke(descriptor);
descriptor.Compose(element);
}
+59 -18
View File
@@ -1,29 +1,70 @@
using QuestPDF.Helpers;
using System;
using QuestPDF.Helpers;
namespace QuestPDF.Infrastructure
{
public class TextStyle
{
internal string Color { get; set; } = Colors.Black;
internal string BackgroundColor { get; set; } = Colors.Transparent;
internal string FontType { get; set; } = "Calibri";
internal float Size { get; set; } = 12;
internal float LineHeight { get; set; } = 1.2f;
internal FontWeight FontWeight { get; set; } = FontWeight.Normal;
internal bool IsItalic { get; set; } = false;
internal bool HasStrikethrough { get; set; } = false;
internal bool HasUnderline { get; set; } = false;
public static TextStyle Default => new TextStyle();
private string? KeyCache { get; set; }
internal bool HasGlobalStyleApplied { get; private set; }
public override string ToString()
internal string? Color { get; set; }
internal string? BackgroundColor { get; set; }
internal string? FontType { get; set; }
internal float? Size { get; set; }
internal float? LineHeight { get; set; }
internal FontWeight? FontWeight { get; set; }
internal bool? IsItalic { get; set; }
internal bool? HasStrikethrough { get; set; }
internal bool? HasUnderline { get; set; }
internal string? Key { get; private set; }
internal static TextStyle LibraryDefault => new TextStyle
{
KeyCache ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}";
return KeyCache;
Color = Colors.Black,
BackgroundColor = Colors.Transparent,
FontType = Fonts.Calibri,
Size = 12,
LineHeight = 1.2f,
FontWeight = Infrastructure.FontWeight.Normal,
IsItalic = false,
HasStrikethrough = false,
HasUnderline = false
};
private static TextStyle DefaultTextStyleCache = new TextStyle();
public static TextStyle Default => DefaultTextStyleCache;
internal void ApplyGlobalStyle(TextStyle globalStyle)
{
if (HasGlobalStyleApplied)
return;
HasGlobalStyleApplied = true;
ApplyParentStyle(globalStyle);
Key ??= $"{Color}|{BackgroundColor}|{FontType}|{Size}|{LineHeight}|{FontWeight}|{IsItalic}|{HasStrikethrough}|{HasUnderline}";
}
internal void ApplyParentStyle(TextStyle parentStyle)
{
Color ??= parentStyle.Color;
BackgroundColor ??= parentStyle.BackgroundColor;
FontType ??= parentStyle.FontType;
Size ??= parentStyle.Size;
LineHeight ??= parentStyle.LineHeight;
FontWeight ??= parentStyle.FontWeight;
IsItalic ??= parentStyle.IsItalic;
HasStrikethrough ??= parentStyle.HasStrikethrough;
HasUnderline ??= parentStyle.HasUnderline;
}
internal TextStyle Clone() => (TextStyle)MemberwiseClone();
internal TextStyle Clone()
{
var clone = (TextStyle)MemberwiseClone();
clone.HasGlobalStyleApplied = false;
return clone;
}
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2021.10.0</Version>
<Version>2021.10.1</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>Enhanced text rendering capabilities. Improved rendering performance.</PackageReleaseNotes>
<LangVersion>8</LangVersion>