Compare commits

..

10 Commits

Author SHA1 Message Date
MarcinZiabek ba4be521aa 2022.2.7 2022-03-12 19:01:39 +01:00
Marcin Ziąbek f6967dfe8b Update readme.md 2022-03-12 16:42:26 +01:00
MarcinZiabek ff3dfe7831 Fixed logo size 2022-03-12 16:39:33 +01:00
MarcinZiabek 5a1990d857 Added more shields 2022-03-12 16:38:03 +01:00
Marcin Ziąbek 9c64a6bc8d Merge pull request #128 from loxsmoke/main
Add nuget badges to readme.md file
2022-03-12 16:06:04 +01:00
MarcinZiabek 5402e5fedc Fixed text rendering issues 2022-03-12 12:17:00 +01:00
MarcinZiabek fdf8e3eb1a Fixed text rendering corner case: measuring span with space at the end of the line may overflow available space 2022-03-08 18:33:34 +01:00
LoxSmoke 456c7a735b Update readme.md 2022-02-24 19:55:36 -08:00
LoxSmoke 5bf2a9a3d3 Nuget badges in readme file 2022-02-24 19:53:51 -08:00
MarcinZiabek 69e268733c 2022.2.5 2022-02-18 23:31:53 +01:00
8 changed files with 155 additions and 61 deletions
+19 -26
View File
@@ -23,7 +23,7 @@ namespace QuestPDF.Examples
.PageSize(PageSizes.A4)
.ProducePdf()
.ShowResults()
.Render(x => ComposeBook(x, chapters));
.RenderDocument(x => ComposeBook(x, chapters));
}
[Test]
@@ -42,8 +42,8 @@ namespace QuestPDF.Examples
RenderingTest
.Create()
.PageSize(PageSizes.A4)
.ProducePdf()
.Render(x => ComposeBook(x, chapters));
.ProducePdf()
.RenderDocument(x => ComposeBook(x, chapters));
}
IEnumerable<float> PerformTest(int attempts)
@@ -102,37 +102,30 @@ namespace QuestPDF.Examples
}
}
private void ComposeBook(IContainer container, ICollection<BookChapter> chapters)
private void ComposeBook(IDocumentContainer container, ICollection<BookChapter> chapters)
{
var subtitleStyle = TextStyle.Default.Size(24).SemiBold().Color(Colors.Blue.Medium);
var normalStyle = TextStyle.Default.Size(14);
ComposePage(container);
void ComposePage(IContainer container)
container.Page(page =>
{
container
.Padding(50)
.Decoration(decoration =>
{
decoration
.Content()
.Column(column =>
{
column.Item().Element(Title);
column.Item().PageBreak();
column.Item().Element(TableOfContents);
column.Item().PageBreak();
page.Margin(50);
page.Content().Column(column =>
{
column.Item().Element(Title);
column.Item().PageBreak();
column.Item().Element(TableOfContents);
column.Item().PageBreak();
Chapters(column);
Chapters(column);
column.Item().Element(Acknowledgements);
});
column.Item().Element(Acknowledgements);
});
page.Footer().Element(Footer);
});
decoration.After().Element(Footer);
});
}
void Title(IContainer container)
{
container
+79 -3
View File
@@ -31,7 +31,8 @@ namespace QuestPDF.Examples
{
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());
text.Span("underlined red text", TextStyle.Default.Color(Colors.Red.Medium).Underline());
text.Span(".");
});
});
}
@@ -189,7 +190,7 @@ namespace QuestPDF.Examples
text.EmptyLine();
text.Span("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
text.Element().PaddingBottom(-4).Height(16).Width(32).Image(Placeholders.Image);
text.Span(".");
text.EmptyLine();
@@ -246,7 +247,7 @@ namespace QuestPDF.Examples
text.Span("and this is slightly bigger text.", TextStyle.Default.Size(16));
text.Span("The new text element also supports injecting custom content between words: ");
text.Element().PaddingBottom(-10).Height(16).Width(32).Image(Placeholders.Image);
text.Element().PaddingBottom(-4).Height(16).Width(32).Image(Placeholders.Image);
text.Span(".");
text.EmptyLine();
@@ -267,5 +268,80 @@ namespace QuestPDF.Examples
});
});
}
[Test]
public void MeasureIssueWhenSpaceAtLineEnd()
{
// issue 135
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.Background(Colors.White);
page.Size(PageSizes.A4);
page.Content().Text(
"This is a specially crafted sentence with a specially chosen length for demonstration of the bug that occurs ;;;;;. ",
TextStyle.Default.Size(11).BackgroundColor(Colors.Red.Lighten3));
});
});
}
[Test]
public void EmptyText()
{
// issue 135
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.Background(Colors.White);
page.Size(PageSizes.A4);
page.Content().Text(
" ",
TextStyle.Default.Size(11).BackgroundColor(Colors.Red.Lighten3));
});
});
}
[Test]
public void Whitespaces()
{
// issue 135
RenderingTest
.Create()
.ProduceImages()
.ShowResults()
.RenderDocument(container =>
{
container.Page(page =>
{
page.Margin(50);
page.Background(Colors.White);
page.Size(PageSizes.A4);
page.Content().Text(
" x ",
TextStyle.Default.Size(11).BackgroundColor(Colors.Red.Lighten3));
});
});
}
}
}
@@ -9,6 +9,6 @@ namespace QuestPDF.Elements.Text.Calculation
public int StartIndex { get; set; }
public float AvailableWidth { get; set; }
public bool IsFirstLineElement { get; set; }
public bool IsFirstElementInLine { get; set; }
}
}
@@ -17,6 +17,6 @@ namespace QuestPDF.Elements.Text.Calculation
public int NextIndex { get; set; }
public int TotalIndex { get; set; }
public bool IsLast => EndIndex == TotalIndex;
public bool IsLast => NextIndex == EndIndex;
}
}
+42 -25
View File
@@ -17,7 +17,7 @@ namespace QuestPDF.Elements.Text.Items
public virtual TextMeasurementResult? Measure(TextMeasurementRequest request)
{
var cacheKey = (request.StartIndex, request.AvailableWidth);
if (!MeasureCache.ContainsKey(cacheKey))
MeasureCache[cacheKey] = MeasureWithoutCache(request);
@@ -33,13 +33,15 @@ namespace QuestPDF.Elements.Text.Items
var startIndex = request.StartIndex;
if (request.IsFirstLineElement)
// if the element is the first one within the line,
// ignore leading spaces
if (request.IsFirstElementInLine)
{
while (startIndex + 1 < Text.Length && Text[startIndex] == space)
while (startIndex < Text.Length && Text[startIndex] == space)
startIndex++;
}
if (Text.Length == 0)
if (Text.Length == 0 || startIndex == Text.Length)
{
return new TextMeasurementResult
{
@@ -52,38 +54,28 @@ namespace QuestPDF.Elements.Text.Items
}
// start breaking text from requested position
var text = Text.Substring(startIndex);
var text = Text.AsSpan().Slice(startIndex);
var textLength = (int)paint.BreakText(text, request.AvailableWidth + Size.Epsilon);
if (textLength <= 0)
return null;
if (textLength < text.Length && text[textLength] == space)
textLength++;
// break text only on spaces
if (textLength < text.Length)
{
var lastSpaceIndex = text.Substring(0, textLength).LastIndexOf(space) - 1;
var wrappedTextLength = WrapText(text, textLength, request.IsFirstElementInLine);
if (lastSpaceIndex <= 0)
{
if (!request.IsFirstLineElement)
return null;
}
else
{
textLength = lastSpaceIndex + 1;
}
}
if (wrappedTextLength == null)
return null;
text = text.Substring(0, textLength);
textLength = wrappedTextLength.Value;
text = text.Slice(0, textLength);
var endIndex = startIndex + textLength;
var nextIndex = endIndex;
while (nextIndex + 1 < Text.Length && Text[nextIndex] == space)
// when breaking text, omit spaces at the end of the line
while (nextIndex < Text.Length && Text[nextIndex] == space)
nextIndex++;
// measure final text
@@ -103,6 +95,31 @@ namespace QuestPDF.Elements.Text.Items
NextIndex = nextIndex,
TotalIndex = Text.Length
};
static int? WrapText(ReadOnlySpan<char> text, int textLength, bool isFirstElementInLine)
{
// textLength - length of the part of the text that fits in available width (creating a line)
// entire text fits, no need to wrap
if (textLength == text.Length)
return textLength;
// current line ends at word, next character is space, perfect place to wrap
if (text[textLength - 1] != space && text[textLength] == space)
return textLength;
// find last space within the available text to wrap
var lastSpaceIndex = text.Slice(0, textLength).LastIndexOf(space);
// text contains space that can be used to wrap
if (lastSpaceIndex > 0)
return lastSpaceIndex;
// there is no available space to wrap text
// if the item is first within the line, perform safe mode and chop the word
// otherwise, move the item into the next line
return isFirstElementInLine ? textLength : null;
}
}
public virtual void Draw(TextDrawingRequest request)
+1 -1
View File
@@ -170,7 +170,7 @@ namespace QuestPDF.Elements.Text
StartIndex = currentItemIndex,
AvailableWidth = availableWidth - currentWidth,
IsFirstLineElement = !currentLineElements.Any()
IsFirstElementInLine = !currentLineElements.Any()
};
var measurementResponse = currentElement.Measure(measurementRequest);
+2 -2
View File
@@ -4,7 +4,7 @@
<Authors>MarcinZiabek</Authors>
<Company>CodeFlint</Company>
<PackageId>QuestPDF</PackageId>
<Version>2022.2.4</Version>
<Version>2022.2.7</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>$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/Resources/ReleaseNotes.txt"))</PackageReleaseNotes>
<LangVersion>9</LangVersion>
@@ -18,7 +18,7 @@
<PackageTags>pdf report file export generate generation tool create creation render portable document format quest html library converter open source free standard core</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Nullable>enable</Nullable>
<TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>net462;netstandard2.0;netcoreapp2.0;netcoreapp3.0;net6.0</TargetFrameworks>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
+10 -2
View File
@@ -1,7 +1,15 @@
<a href="https://www.questpdf.com/">
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/logo.svg" width="400px">
<a href="https://www.questpdf.com/" target="_blank">
<img src="https://github.com/QuestPDF/example-invoice/raw/main/images/logo.svg" width="300">
</a>
---
[![GitHub Repo stars](https://img.shields.io/github/stars/QuestPDF/QuestPDF)](https://github.com/QuestPDF/QuestPDF/stargazers)
[![Nuget version](https://img.shields.io/nuget/v/QuestPdf)](https://www.nuget.org/packages/QuestPDF/)
[![Nuget download](https://img.shields.io/nuget/dt/QuestPDF)](https://www.nuget.org/packages/QuestPDF/)
[![License](https://img.shields.io/github/license/QuestPDF/QuestPDF)](https://github.com/QuestPDF/QuestPDF/blob/main/LICENSE)
[![Sponsor project](https://img.shields.io/badge/sponsor-project-red)](https://github.com/sponsors/QuestPDF)
QuestPDF presents a new approach to PDF document generation. Unlike other libraries, it does not rely on the HTML-to-PDF conversion which in many cases is not reliable. Instead, it implements its own layouting engine that is optimized to cover all paging-related requirements. Then, everything is rendered using the SkiaSharp library (a Skia port for .NET, used in Chrome, Android, MAUI, etc.).
I have designed this layouting engine with full paging support in mind. The document consists of many simple elements (e.g. border, background, image, text, padding, table, grid etc.) that are composed together to create more complex structures. This way, as a developer, you can understand the behaviour of every element and use them with full confidence. Additionally, the document and all its elements support paging functionality. For example, an element can be moved to the next page (if there is not enough space) or even be split between pages like table's rows.