Skip to content

Commit 6e7f9f4

Browse files
committed
Implement more feaures that require minimal changes in the DOCX renderer and add relevant samples
1 parent 4e71c53 commit 6e7f9f4

File tree

12 files changed

+119
-10
lines changed

12 files changed

+119
-10
lines changed

src/WIP/DocSharp.Renderer/DocxRenderer.cs

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,28 @@ internal override void ProcessSection((List<OpenXmlElement> content, SectionProp
132132
l = margins.Left.Value;
133133
if (margins.Right != null)
134134
r = margins.Right.Value;
135-
}
135+
}
136136

137137
// Convert twips to points
138138
var pageSet = new QuestPdfPageSet(w / 20f, h / 20f, l / 20f, t / 20f, r / 20f, b / 20f,
139139
QuestPDF.Infrastructure.Unit.Point);
140+
141+
var columns = sectionProperties.GetFirstChild<Columns>();
142+
if (columns != null && columns.ColumnCount != null && columns.ColumnCount > 1)
143+
{
144+
pageSet.NumberOfColumns = columns.ColumnCount.Value;
145+
146+
if (columns.Space.ToFloat() is float columnGap && columnGap > 0)
147+
{
148+
pageSet.SpaceBetweenColumns = columnGap / 20f; // Convert twips to points
149+
}
150+
151+
if (columns.EqualWidth != null && columns.EqualWidth.Value == false)
152+
{
153+
// TODO
154+
}
155+
}
156+
140157
if (pageColor.HasValue)
141158
pageSet.BackgroundColor = pageColor.Value;
142159

@@ -145,8 +162,7 @@ internal override void ProcessSection((List<OpenXmlElement> content, SectionProp
145162

146163
// Process headers for this section
147164
var headerRefs = sectionProperties.Elements<HeaderReference>();
148-
// QuestPDF can't produce different header and footer on odd/even pages.
149-
// For now, handle the default header and footer only.
165+
// TODO: QuestPDF can produce different header and footer on odd/even pages using the ShowIf() function.
150166
var headerRef = headerRefs.FirstOrDefault(h => h.Type == null || !h.Type.HasValue || h.Type.Value == HeaderFooterValues.Default);
151167
if (headerRef?.Id?.Value is string headerId && mainPart.GetPartById(headerId) is HeaderPart headerPart)
152168
{
@@ -158,8 +174,7 @@ internal override void ProcessSection((List<OpenXmlElement> content, SectionProp
158174

159175
// Process footers for this section
160176
var footerRefs = sectionProperties.Elements<FooterReference>();
161-
// QuestPDF can't produce different header and footer on odd/even pages.
162-
// For now, handle the default header and footer only.
177+
// TODO: QuestPDF can produce different header and footer on odd/even pages using the ShowIf() function.
163178
var footerRef = footerRefs.FirstOrDefault(h => h.Type == null || !h.Type.HasValue || h.Type.Value == HeaderFooterValues.Default);
164179
if (footerRef?.Id?.Value is string footerId && mainPart.GetPartById(footerId) is FooterPart footerPart)
165180
{
@@ -214,6 +229,9 @@ internal override void ProcessParagraph(Paragraph paragraph, QuestPdfModel outpu
214229
p.EndIndent = indent.EndIndent;
215230
p.FirstLineIndent = indent.FirstLineIndent;
216231

232+
p.KeepTogether = paragraph.GetEffectiveProperty<KeepLines>().ToBool();
233+
// TODO: KeepNext (cannot be set at paragraph level in QuestPDF and requires a different approach)
234+
217235
// Add paragraph to the current container (body, header, footer, table cell, ...)
218236
if (currentContainer.Count > 0)
219237
currentContainer.Peek().Content.Add(p);
@@ -396,7 +414,8 @@ internal override void ProcessBreak(Break @break, QuestPdfModel output)
396414
// Add span to the paragraph/hyperlink
397415
newRunContainer.AddSpan(newSpan);
398416

399-
// If the run container is an hyperlink, enclose it into a new paragraph
417+
// If the run container is an hyperlink, enclose it into a new paragraph,
418+
// otherwise the run container is the container itself.
400419
QuestPdfParagraph newParagraph;
401420
if (newRunContainer is QuestPdfParagraph paragraph)
402421
{
@@ -428,7 +447,47 @@ internal override void ProcessBreak(Break @break, QuestPdfModel output)
428447
}
429448
else if (@break.Type.HasValue && @break.Type.Value == BreakValues.Page)
430449
{
450+
// Close and retrieve the current span, run container (paragraph/hyperlink) and paragraph
451+
var oldSpan = currentSpan.Pop();
452+
var oldRunContainer = currentRunContainer.Pop();
453+
var oldParagraph = currentParagraph.Pop();
454+
455+
// Add a new QuestPdfPageBreak object
456+
currentContainer.Peek().Content.Add(new QuestPdfPageBreak());
457+
458+
// The old span and paragraph were closed ahead of time to process the Break element.
459+
// Create a new paragraph and span with the same properties to contain further elements.
460+
461+
// Create a new run container and span
462+
var newRunContainer = oldRunContainer.CloneEmpty();
463+
var newSpan = oldSpan.CloneEmpty();
464+
465+
// Add span to the paragraph/hyperlink
466+
newRunContainer.AddSpan(newSpan);
467+
468+
// If the run container is an hyperlink, enclose it into a new paragraph,
469+
// otherwise the run container is the container itself.
470+
QuestPdfParagraph newParagraph;
471+
if (newRunContainer is QuestPdfParagraph paragraph)
472+
{
473+
newParagraph = paragraph;
474+
}
475+
else
476+
{
477+
newParagraph = (QuestPdfParagraph)(oldParagraph.CloneEmpty());
478+
if (newRunContainer is QuestPdfHyperlink hyperlink)
479+
{
480+
newParagraph.Elements.Add(hyperlink);
481+
}
482+
}
431483

484+
// Set current span, run container and paragraph
485+
currentParagraph.Push(newParagraph);
486+
currentRunContainer.Push(newRunContainer);
487+
currentSpan.Push(newSpan);
488+
489+
// Add paragraph to the current container (body, header, footer, table cell, ...)
490+
currentContainer.Peek().Content.Add(newParagraph);
432491
}
433492
else if (@break.Type.HasValue && @break.Type.Value == BreakValues.Column)
434493
{
@@ -479,6 +538,27 @@ internal override void ProcessSymbolChar(SymbolChar symbolChar, QuestPdfModel ou
479538
}
480539
}
481540

541+
internal override void ProcessPageNumber(PageNumber pageNumber, QuestPdfModel output)
542+
{
543+
if (currentRunContainer.Count > 0 &&
544+
currentSpan.Count > 0) // PageNumber can only be present inside a Run, just like regular Text elements.
545+
{
546+
// Close and retrieve the current span
547+
var oldSpan = currentSpan.Pop();
548+
549+
// Add a new QuestPdfPageNumber object to the current run container.
550+
currentRunContainer.Peek().AddPageNumber();
551+
552+
// The old span was closed ahead of time to process the PageNumber element.
553+
// Create a new span with the same properties to contain further text elements.
554+
// The new span will be closed by the ProcessRun method.
555+
// If there are no remaining elements, the new span will be empty
556+
// and will be ignored during rendering.
557+
var newSpan = oldSpan.CloneEmpty();
558+
currentSpan.Push(newSpan);
559+
}
560+
}
561+
482562
internal override void ProcessTable(Table table, QuestPdfModel output)
483563
{
484564
// Process table properties and create a new QuestPdfTable object
@@ -553,10 +633,6 @@ internal override void ProcessBookmarkEnd(BookmarkEnd bookmarkEnd, QuestPdfModel
553633
{
554634
}
555635

556-
internal override void ProcessPageNumber(PageNumber pageNumber, QuestPdfModel output)
557-
{
558-
}
559-
560636
internal override void ProcessAnnotationReference(AnnotationReferenceMark annotationRef, QuestPdfModel output)
561637
{
562638
}

src/WIP/DocSharp.Renderer/Model/IQuestPdfRunContainer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace DocSharp.Renderer;
33
internal interface IQuestPdfRunContainer
44
{
55
void AddSpan(QuestPdfSpan span);
6+
void AddPageNumber();
67
internal IQuestPdfRunContainer CloneEmpty();
78
}
89

src/WIP/DocSharp.Renderer/Model/QuestPdfHyperlink.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ public void AddSpan(QuestPdfSpan span)
2828
Elements.Add(span);
2929
}
3030

31+
public void AddPageNumber()
32+
{
33+
Elements.Add(new QuestPdfPageNumber());
34+
}
35+
3136
public IQuestPdfRunContainer CloneEmpty()
3237
{
3338
return new QuestPdfHyperlink(Url, Anchor);

src/WIP/DocSharp.Renderer/Model/QuestPdfModel.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ internal void CreateColumn(ColumnDescriptor column, List<QuestPdfBlock> elements
132132
// Why is LineHeight at the span level in QuestPDF rather than at the same level as ParagraphFirstLineIndentation?
133133
text.Span(span.IsAllCaps ? span.Text.ToUpper() : span.Text).Style(span.Style).LineHeight(paragraph.LineHeight);
134134
}
135+
else if (inline is QuestPdfPageNumber pageNumber)
136+
{
137+
text.CurrentPageNumber();
138+
}
135139
else if (inline is QuestPdfHyperlink hyperlink)
136140
{
137141
// Adding multiple formatted spans at once inside the link is not possible, so we add multiple spans with the same URL.
@@ -152,6 +156,7 @@ internal void CreateColumn(ColumnDescriptor column, List<QuestPdfBlock> elements
152156
}
153157
else if (element is QuestPdfTable table)
154158
{
159+
// Start a new table
155160
column.Item().Table(t =>
156161
{
157162
t.ColumnsDefinition(c =>
@@ -185,6 +190,11 @@ internal void CreateColumn(ColumnDescriptor column, List<QuestPdfBlock> elements
185190

186191
});
187192
}
193+
else if (element is QuestPdfPageBreak)
194+
{
195+
// Force page break
196+
column.Item().PageBreak();
197+
}
188198
}
189199
}
190200
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace DocSharp.Renderer;
2+
3+
internal class QuestPdfPageBreak : QuestPdfBlock
4+
{
5+
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace DocSharp.Renderer;
2+
3+
internal class QuestPdfPageNumber : QuestPdfInlineElement
4+
{
5+
6+
}

src/WIP/DocSharp.Renderer/Model/QuestPdfParagraph.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ public void AddSpan(QuestPdfSpan span)
4747
Elements.Add(span);
4848
}
4949

50+
public void AddPageNumber()
51+
{
52+
Elements.Add(new QuestPdfPageNumber());
53+
}
54+
5055
public IQuestPdfRunContainer CloneEmpty()
5156
{
5257
return new QuestPdfParagraph(Alignment, LineHeight, SpaceBefore, SpaceAfter, LeftIndent, RightIndent, StartIndent, EndIndent, FirstLineIndent, BackgroundColor, KeepTogether);

test-files/Renderer/Breaks.docx

13.2 KB
Binary file not shown.

test-files/Renderer/Fields.docx

20.5 KB
Binary file not shown.
13.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)