Commit 47ab6f5f authored by Nuno Alves's avatar Nuno Alves
Browse files

set italic style for specific expressions; change report section header data...

set italic style for specific expressions; change report section header data type and adjust code accordingly;
parent bdf84c3f
......@@ -2,7 +2,7 @@
In diesem Repository geht es um die Generierung von PDF-Dokumenten über einen Microservice via Web API. Eine WebUi wird zusätzlich zum Testen von Anfragen mit Dateneingabe bereitgestellt.
Grundsätzlich werden befüllte Datenmodelle über einen Webservice an einen PDFGenerator Microservice gesendet, der dafür sorgt, PDF-Dateien aus den empfangengen Datenmodellen zu erzeugen. Vom Webserver wird entweder ein Evaluationsbericht oder ein Factsheet je nach aufgerufenem URI generiert und das entsprechende PDF-Dokument als byte Array zurückgegeben.
Grundsätzlich werden befüllte Datenmodelle über einen Webservice an einen PDFGenerator Microservice gesendet, der dafür sorgt, PDF-Dateien aus den empfangengen Datenmodellen zu erzeugen. Vom Webserver wird entweder ein Evaluationsbericht oder ein Fac Sheet je nach aufgerufenem URI generiert und das entsprechende PDF-Dokument als byte Array zurückgegeben.
Der Microservice-Server Web API widmet sich der Generierung von Pdf-Dokumenten anhand der verwendeten _iText7_-Bibliothek. _iText_ ist eine freie Programmbibliothek zur Erzeugung und Bearbeitung von PDF-Dateien mittels der Programmiersprachen _Java_ oder _C#_, deren Software unter der [GNU Affero General Public License](https://de.wikipedia.org/wiki/GNU_Affero_General_Public_License) (AGPL) vertrieben wird.
......@@ -31,14 +31,35 @@ public class EvaluationReport
Dieses Datenmodell befindet sich unter `pdf-generator\Models\EvaluationReport.cs`.
### Datenmodell für das Factsheet
### Datenmodell für das Fact Sheet
Text
Zu den Bestandteilen eines Fact Sheet zählen das Deckblatt (Cover), eine Kopf- und Fußzeile (Header, Footer) sowie eine Menge von Kapiteln, in denen mehrere Reports jeweils dargestellt werden. Außerdem gibt es eine "Keine Daten vorhanden"-Meldung, welche falls zutreffend in jedem Kapitel anwendbar ist:
```csharp
public class FactSheet
{
public PDFHeaderFooter Header { get; set; }
public PDFHeaderFooter Footer { get; set; }
public PDFCover Cover { get; set; }
public PDFChapter[] Chapters { get; set; }
public Headline NoDataAvailable { get; set; }
}
```
Dieses Datenmodell befindet sich unter `pdf-generator\Models\FactSheet.cs`.
## Generierung des Evaluationsberichts
Als Erstes wird ein _EvaluationReportRenderer_ mit dem EvaluationReport-Inhalt aus dem Request-Body instantiiert, der für die Erstellung des PDF-Dokuments mit dem zugehörigen Inhalt zuständig ist.
Als Erstes wird in _EvaluationReportController_ ein _EvaluationReportRenderer_ mit dem EvaluationReport-Inhalt aus dem Request-Body instantiiert, der für die Erstellung des PDF-Dokuments mit dem zugehörigen Inhalt zuständig ist.
Mittels _RenderSections_ wird danach jeder einzelner Abschnitt ins Dokument geschrieben. Da das Inhaltsverzeichnis als letzter Abschnitt gerendert wird und direkt hinter dem Deckblatt liegen soll, werden die Seiten anschließend umsortiert (_MoveTocToFront_). Als Nächtes wird eine Kopfzeile fürs ganze Dokument geschrieben (_AddHeader_).
Zuletzt wird das Dokument als byte-Array zurückgegeben.
## Fact Sheet Generierung
In _FactSheetController_ befindet sich die vom _WebClient_ aufgerufene Methode RenderFactSheet, in welcher der _FactSheetRenderer_ mit dem befüllten Fact Sheet-Datenmodell instanziiert wird, das für dein Einbau jedes Bestandteils durch die Methode _Render_ sorgt. Daraus ergibt sich ein an den Peek-Client zurückgebendes byte Array.
In der Methode _Render_ werden zunächst das Deckblatt (_RenderCover_), gefolgt von dem jeweiligen Kapitel (_RenderChapters_) ins Dokument geschrieben. Anschließend wird dem ganzen Dokument Kopf- und Fußzeile hinzugefügt (_RenderHeaderFooter_).
Zum Schluss wird das Dokument von einem Stream in Array umgewandelt und an den _WebClient_ zurückgesendet.
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using PDFGenerator.Renderers;
using EvaluationReportModel = PDFGenerator.Models.EvaluationReport.EvaluationReport;
namespace PDFGenerator.Controllers
{
......@@ -8,11 +9,11 @@ namespace PDFGenerator.Controllers
[Route("[controller]")]
public class EvaluationReportController : ControllerBase
{
private readonly ILogger<EvaluationReportController> _logger;
private ILogger<EvaluationReportController> Logger { get; }
public EvaluationReportController(ILogger<EvaluationReportController> logger)
{
_logger = logger;
Logger = logger;
}
/// <summary>
......@@ -32,14 +33,14 @@ namespace PDFGenerator.Controllers
/// <returns>PDF document as byte array.</returns>
[HttpPost("RenderReport")]
[RequestSizeLimit(100000000)]
public byte[] RenderReport([FromBody] PDFGenerator.Models.EvaluationReport evaluationReport)
public byte[] RenderReport([FromBody] EvaluationReportModel evaluationReport)
{
_logger.Log(LogLevel.Debug, "Start rendering pdf report.");
Logger.Log(LogLevel.Debug, "Start rendering pdf report.");
IRenderer Renderer = new EvaluationReportRenderer(evaluationReport);
byte[] file = Renderer.Render();
_logger.Log(LogLevel.Debug, "Finished rendering pdf report.");
Logger.Log(LogLevel.Debug, "Finished rendering pdf report.");
return file;
}
}
......
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using PDFGenerator.Renderers;
using FactSheetModel = PDFGenerator.Models.FactSheet.FactSheet;
namespace PDFGenerator.Controllers
{
[Route("[controller]")]
[ApiController]
public class FactSheetController : ControllerBase
{
private ILogger<FactSheetController> Logger { get; }
public FactSheetController(ILogger<FactSheetController> logger)
{
Logger = logger;
}
/// <summary>
/// Say hello!
/// </summary>
/// <returns>"Hello"</returns>
[HttpGet("Hello")]
public string Get()
{
return "Hello";
}
/// <summary>
/// Generates the PDF for a factSheet.
/// </summary>
/// <param name="factSheet">FactSheet for which the PDF should be rendered.</param>
/// <returns>PDF document as byte array.</returns>
[HttpPost("RenderFactSheet")]
[RequestSizeLimit(100000000)]
public byte[] RenderFactSheet([FromBody] FactSheetModel factSheet)
{
Logger.Log(LogLevel.Debug, "Start rendering pdf for the Fact Sheet.");
IRenderer Renderer = new FactSheetRenderer(factSheet);
byte[] file = Renderer.Render();
Logger.Log(LogLevel.Debug, "Finished rendering pdf for the Fact Sheet.");
return file;
}
}
}
using iText.Kernel.Geom;
using iText.Layout;
using iText.Layout.Element;
using iText.Layout.Properties;
namespace PDFGenerator.DocumentStructures
{
public abstract class BaseSection
{
protected Document Document { get; set; }
public BaseSection(Document document)
{
Document = document;
}
/// <summary>
/// Adds page break to document.
/// </summary>
public void AddPageBreak()
{
Document.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
/// <summary>
/// Gets default page size.
/// </summary>
/// <returns>The default page size.</returns>
protected PageSize GetPageSize()
{
return Document.GetPdfDocument().GetDefaultPageSize();
}
}
}
......@@ -4,17 +4,19 @@ using iText.Kernel.Pdf.Xobject;
using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using PDFGenerator.Models;
using PDFGenerator.Models.EvaluationReport;
using PDFGenerator.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using static PDFGenerator.Utilities.Globals;
using Styles = PDFGenerator.Utilities.Globals.Constants.EvaluationReport.Styles;
namespace PDFGenerator.EvaluationReport
namespace PDFGenerator.DocumentStructures.EvaluationReport
{
internal class Appendix : BaseSection
internal class Appendix : EvaluationReportBaseSection
{
private PDFAppendix PDFAppendix { get; }
private bool FirstFile { get; set; }
......
using iText.Layout;
using iText.Layout.Element;
using PDFGenerator.Models;
using PDFGenerator.Models.EvaluationReport;
using PDFGenerator.Utilities;
using System.Collections.Generic;
using System.Linq;
using static PDFGenerator.Utilities.Globals;
using Styles = PDFGenerator.Utilities.Globals.Constants.EvaluationReport.Styles;
namespace PDFGenerator.EvaluationReport
namespace PDFGenerator.DocumentStructures.EvaluationReport
{
public class CourseGeneralInformation : BaseSection
public class CourseGeneralInformation : EvaluationReportBaseSection
{
private PDFCourseGeneralInformation Information { get; }
public Dictionary<string, byte[]> CustomUploads { get; private set; }
......
......@@ -2,15 +2,16 @@ using iText.Kernel.Colors;
using iText.Layout;
using iText.Layout.Element;
using iText.Layout.Properties;
using PDFGenerator.Models;
using PDFGenerator.Models.EvaluationReport;
using PDFGenerator.Utilities;
using System.Collections.Generic;
using System.Linq;
using static PDFGenerator.Utilities.Globals;
using Styles = PDFGenerator.Utilities.Globals.Constants.EvaluationReport.Styles;
namespace PDFGenerator.EvaluationReport
namespace PDFGenerator.DocumentStructures.EvaluationReport
{
public class CourseInformationTables : BaseSection
public class CourseInformationTables : EvaluationReportBaseSection
{
private PDFCourseInformationTables Tables { get; }
public CourseInformationTables(PDFCourseInformationTables tables, Document document, Bookmarks bookmarks, TableOfContents tableOfContents)
......
......@@ -3,15 +3,16 @@ using iText.Kernel.Geom;
using iText.Layout;
using iText.Layout.Element;
using iText.Layout.Properties;
using PDFGenerator.Models;
using PDFGenerator.Models.EvaluationReport;
using PDFGenerator.Utilities;
using System;
using static PDFGenerator.Utilities.Conversion;
using Styles = PDFGenerator.Utilities.Globals.Constants.EvaluationReport.Styles;
namespace PDFGenerator.EvaluationReport
namespace PDFGenerator.DocumentStructures.EvaluationReport
{
public class Cover : BaseSection
public class Cover : EvaluationReportBaseSection
{
private PDFCover PDFCover { get; }
......
......@@ -5,11 +5,12 @@ using PDFGenerator.Utilities;
using System.Collections.Generic;
using System.Linq;
using static PDFGenerator.Utilities.Globals;
using Target = PDFGenerator.Models.CriterionTarget;
using Styles = PDFGenerator.Utilities.Globals.Constants.EvaluationReport.Styles;
using Target = PDFGenerator.Models.EvaluationReport.CriterionTarget;
namespace PDFGenerator.EvaluationReport
namespace PDFGenerator.DocumentStructures.EvaluationReport
{
public class CriterionTarget : BaseSection
public class CriterionTarget : EvaluationReportBaseSection
{
private Target Target { get; }
private int SectionIndent { get; }
......
......@@ -6,26 +6,25 @@ using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using iText.Layout.Properties;
using PDFGenerator.Models;
using PDFGenerator.Models.EvaluationReport;
using PDFGenerator.Renderers;
using PDFGenerator.Utilities;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using static PDFGenerator.Utilities.Globals;
namespace PDFGenerator.EvaluationReport
namespace PDFGenerator.DocumentStructures.EvaluationReport
{
public abstract class BaseSection
public abstract class EvaluationReportBaseSection : BaseSection
{
protected Document Document { get; set; }
protected PdfOutline RootOutline { get; }
protected Bookmarks Bookmarks { get; }
protected TableOfContents TableOfContents { get; }
protected static Text NewLine { get; } = new Text("\n");
public BaseSection(Document document, Bookmarks bookmarks, TableOfContents tableOfContents)
public EvaluationReportBaseSection(Document document, Bookmarks bookmarks, TableOfContents tableOfContents) : base(document)
{
Document = document;
Bookmarks = bookmarks;
TableOfContents = tableOfContents;
......@@ -47,15 +46,6 @@ namespace PDFGenerator.EvaluationReport
return Document.GetPdfDocument().GetNumberOfPages();
}
/// <summary>
/// Gets default page size.
/// </summary>
/// <returns>The default page size.</returns>
protected PageSize GetPageSize()
{
return Document.GetPdfDocument().GetDefaultPageSize();
}
/// <summary>
/// Resizes image to fit page size.
/// </summary>
......@@ -147,7 +137,7 @@ namespace PDFGenerator.EvaluationReport
/// Adds paragraph above the table.
/// </summary>
/// <param name="table">The table on which the paragraph is added.</param>
/// <param name="text">The paragraphs content.<param>
/// <param name="text">The paragraphs content.</param>
/// <param name="header">Flag indicating whether the content is a table header.</param>
/// <param name="style">Given style for paragraph.</param>
protected void AddParagraphWithLeadingToTable(Table table, string text, bool header, Style style = null)
......@@ -205,14 +195,6 @@ namespace PDFGenerator.EvaluationReport
return "\u0000" + Regex.Replace(text, @"\n", "\n\u0000");
}
/// <summary>
/// Adds page break to document.
/// </summary>
public void AddPageBreak()
{
Document.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
/// <summary>
/// Adds scaled image to document.
/// </summary>
......
......@@ -3,14 +3,15 @@ using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using iText.Layout.Properties;
using PDFGenerator.Models;
using PDFGenerator.Models.EvaluationReport;
using PDFGenerator.Utilities;
using System.Collections.Generic;
using static PDFGenerator.Utilities.Globals;
using Styles = PDFGenerator.Utilities.Globals.Constants.EvaluationReport.Styles;
namespace PDFGenerator.EvaluationReport
namespace PDFGenerator.DocumentStructures.EvaluationReport
{
public class EvaluationResults : BaseSection
public class EvaluationResults : EvaluationReportBaseSection
{
private PDFEvaluationResults Results { get; }
public Dictionary<string, byte[]> CustomUploads { get; private set; }
......
......@@ -2,14 +2,15 @@ using iText.Kernel.Colors;
using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using PDFGenerator.Models;
using PDFGenerator.Models.EvaluationReport;
using PDFGenerator.Utilities;
using System.Collections.Generic;
using KPIEvaluation = PDFGenerator.Models.KpiEvaluation;
using KPIEvaluation = PDFGenerator.Models.EvaluationReport.KpiEvaluation;
using Styles = PDFGenerator.Utilities.Globals.Constants.EvaluationReport.Styles;
namespace PDFGenerator.EvaluationReport
namespace PDFGenerator.DocumentStructures.EvaluationReport
{
public class KpiEvaluation : BaseSection
public class KpiEvaluation : EvaluationReportBaseSection
{
private KPIEvaluation Evaluation { get; }
private Table Table { get; set; }
......
......@@ -3,14 +3,15 @@ using iText.Kernel.Pdf.Action;
using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using PDFGenerator.Models;
using PDFGenerator.Models.EvaluationReport;
using PDFGenerator.Utilities;
using System.Collections.Generic;
using Reports = PDFGenerator.Models.KpiEvaluationReports;
using Reports = PDFGenerator.Models.EvaluationReport.KpiEvaluationReports;
using Styles = PDFGenerator.Utilities.Globals.Constants.EvaluationReport.Styles;
namespace PDFGenerator.EvaluationReport
namespace PDFGenerator.DocumentStructures.EvaluationReport
{
public class KpiEvaluationReports : BaseSection
public class KpiEvaluationReports : EvaluationReportBaseSection
{
private Reports Reports { get; }
private Table Table { get; set; }
......
......@@ -3,14 +3,15 @@ using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using iText.Layout.Properties;
using PDFGenerator.Models;
using PDFGenerator.Models.EvaluationReport;
using PDFGenerator.Utilities;
using System.Linq;
using static PDFGenerator.Utilities.Globals;
using Styles = PDFGenerator.Utilities.Globals.Constants.EvaluationReport.Styles;
namespace PDFGenerator.EvaluationReport
namespace PDFGenerator.DocumentStructures.EvaluationReport
{
public class Preface : BaseSection
public class Preface : EvaluationReportBaseSection
{
private PDFPreface PDFPreface { get; }
......
......@@ -5,11 +5,11 @@ using iText.Kernel.Pdf.Canvas.Draw;
using iText.Layout;
using iText.Layout.Element;
using iText.Layout.Properties;
using PDFGenerator.Utilities;
using System.Collections.Generic;
using System.IO;
using Styles = PDFGenerator.Utilities.Globals.Constants.EvaluationReport.Styles;
namespace PDFGenerator.EvaluationReport
namespace PDFGenerator.DocumentStructures.EvaluationReport
{
public struct TocItem
{
......
using iText.IO.Image;
using iText.Kernel.Geom;
using iText.Layout;
using iText.Layout.Borders;
using iText.Layout.Element;
using iText.Layout.Layout;
using iText.Layout.Properties;
using PDFGenerator.Models.FactSheet;
using PDFGenerator.Utilities;
using System.Collections.Generic;
using System.Linq;
namespace PDFGenerator.DocumentStructures.FactSheet
{
public class Chapter : FactSheetBaseSection
{
private PDFChapter PDFChapter { get; set; }
private Headline CommonMessage { get; }
private Table Table { get; set; }
private string[] ItalicStyleForExpressions { get; } = new string[] { "kohorte" };
// control variables
private int ReportSectionElementCounter { get; set; }
public Chapter(Headline commonMessage, Document document) : base(document)
{
CommonMessage = commonMessage;
}
/// <summary>
/// Updates the chapter to render.
/// </summary>
/// <param name="chapter"></param>
public void UpdateChapter(PDFChapter chapter)
{
PDFChapter = chapter;
}
/// <summary>
/// Renders the content for a chapter.
/// </summary>
public void Render()
{
// add page break at the chapter begin
AddPageBreak();
Table = new Table(1, false)
.UseAllAvailableWidth().SetKeepTogether(false);
AddParagrpahToTable(ParagraphFactory.GetCustomParagraph(PDFChapter.Title.Name, HeadlineLevel[PDFChapter.Title.Level], 5.0f, 0, 1.12f));
for (int i = 0; i < PDFChapter.ReportSections.Count; i++)
{
ReportSectionElementCounter = 0;
bool reportSectionRendered = false;
while (!reportSectionRendered)
{
if (PDFChapter.ReportSections[i].Headlines != null)
{
foreach (Headline headline in PDFChapter.ReportSections[i].Headlines)
{
// give hihgher space in between report sections, if the report section is neither the first nor the current position is on the page top
float spacinfBefore = i == 0 || IsCurrentPositionOnPageTop() || headline.Name != PDFChapter.ReportSections[i].Headlines[0].Name ? 3.0f : 15.0f;
bool isItalic = ItalicStyleForExpressions.Any(expression => headline.Name.ToLower().Contains(expression));
AddParagrpahToTable(ParagraphFactory.GetCustomParagraph(headline.Name, HeadlineLevel[headline.Level], spacinfBefore, 0, 1.1f, isItalic));
}
}
if (PDFChapter.ReportSections[i].Header != null)
{
AddParagrpahToTable(
ParagraphFactory.GetCustomParagraph(PDFChapter.ReportSections[i].Header.Name, HeadlineLevel[PDFChapter.ReportSections[i].Header.Level], 9.0f, 0, 1.12f)
.SetTextAlignment(TextAlignment.CENTER)
);
}
if (PDFChapter.ReportSections[i].Images != null)
{
if (!AddImagesToTable(PDFChapter.ReportSections[i].Images))
{
MoveNextReportSectionToNextPage();
continue;
}
}
else
{
// display the no data available message
AddParagrpahToTable(ParagraphFactory.GetCustomParagraph(CommonMessage.Name, HeadlineLevel[CommonMessage.Level], 8.0f, 11.0f, 1.12f));
}
reportSectionRendered = true;
}
}
Document.Add(Table);
}
/// <summary>
/// Adds a single report.
/// </summary>
/// <param name="images">List of images representing the report.</param>
private bool AddImagesToTable(List<byte[]> images)
{
ImageData imageData;
Image image;
for (int i = 0; i < images.Count; i++)
{
float currentHeight = RemainingTableHeight();
imageData = ImageDataFactory.Create(images[i]);
image = new Image(imageData);
Cell cell = new Cell()
.Add(GetScaledImage(image))
.SetBorder(Border.NO_BORDER)
.SetMargin(0)
.SetPadding(0)
.SetPaddingTop(6f);
Table.AddCell(cell);
ReportSectionElementCounter++;
float nextHeight = RemainingTableHeight();
if (currentHeight == nextHeight)
{
if (i == 0)
{
RemoveReportSectionElementsFromTable();
return false;
}
ReportSectionElementCounter = 1;
RemoveReportSectionElementsFromTable();
MoveNextReportSectionToNextPage();
i--;
}
}
return true;
}
/// <summary>
/// Resizes image to fit page size.