diff --git a/QuizTools.slnx b/QuizTools.slnx
new file mode 100644
index 0000000..29b7daf
--- /dev/null
+++ b/QuizTools.slnx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/QuizTools/Program.cs b/QuizTools/Program.cs
new file mode 100644
index 0000000..e0d60f2
--- /dev/null
+++ b/QuizTools/Program.cs
@@ -0,0 +1,382 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.IO;
+using System.Linq;
+using Dapper;
+using Microsoft.Data.SqlClient;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+using QuestPDF.Infrastructure;
+
+namespace QuizTools
+{
+ internal class Program
+ {
+ const int MaxQuestionImageHeight = 60;
+ const int MaxAnswerImageHeight = 40;
+ static void Main(string[] args)
+ {
+ // 1. License Configuration
+ QuestPDF.Settings.License = LicenseType.Community;
+
+ string connectionString = "Server=localhost;Database=idpbw;Trusted_Connection=True;TrustServerCertificate=True;";
+
+ Console.WriteLine("Fetching data from SQL Server...");
+ var modules = FetchQuizData(connectionString);
+
+ Console.WriteLine("Generating PDF Booklet...");
+ GeneratePdfBooklet(modules, "Quiz_Review_Booklet.pdf");
+
+ Console.WriteLine("PDF generated successfully!");
+ }
+
+ // --- Data Models ---
+ public class ModuleModel
+ {
+ public int Id { get; set; }
+ public string NameNL { get; set; } = string.Empty;
+ public string NameFR { get; set; } = string.Empty;
+ public string NameEN { get; set; } = string.Empty;
+ public List Questions { get; set; } = new();
+ }
+
+ public class QuestionModel
+ {
+ public int Id { get; set; }
+ public byte[]? Image { get; set; }
+ public Dictionary Text { get; set; } = new();
+ public List Answers { get; set; } = new();
+ }
+
+ public class AnswerModel
+ {
+ public int Id { get; set; }
+ public bool IsCorrect { get; set; }
+ public byte[]? Image { get; set; }
+ public Dictionary Text { get; set; } = new();
+ }
+
+ // --- Database Fetching ---
+ static List FetchQuizData(string connStr)
+ {
+ using IDbConnection db = new SqlConnection(connStr);
+ string query = @"
+SELECT m.moduleId AS ModuleId, m.naamNL AS ModuleNameNL,m.naamFR AS ModuleNameFR, m.naamEN AS ModuleNameEN, q.vraagId AS QuestionId, q.NL AS QTextNL, q.FR AS QTextFR, q.EN AS QTextEN, q_img.Image AS QImage, a.antwId AS AnswerId, a.NL AS ATextNL, a.FR AS ATextFR, a.EN AS ATextEN, a.correct AS IsCorrect, a_img.Image AS AImage
+FROM dbo.modules m
+ JOIN quiz.vragen q ON m.moduleId=q.moduleId
+ LEFT JOIN dbo.QuizImages q_img ON q.QuizImageId=q_img.QuizImageId
+ JOIN quiz.antwoorden a ON q.vraagId=a.vraagId
+ LEFT JOIN dbo.QuizImages a_img ON a.QuizImageId=a_img.QuizImageId
+ WHERE q.active=1 AND q.deleted=0
+ORDER BY m.moduleId, q.vraagId, a.antwId;
+";
+
+ var rows = db.Query(query);
+ var modulesDict = new Dictionary();
+
+ foreach (var row in rows)
+ {
+ int modId = row.ModuleId;
+ if (!modulesDict.TryGetValue(modId, out var module))
+ {
+ module = new ModuleModel
+ {
+ Id = modId,
+ NameNL = row.ModuleNameNL ?? string.Empty,
+ NameFR = row.ModuleNameFR ?? string.Empty,
+ NameEN = row.ModuleNameEN ?? string.Empty
+ };
+ modulesDict[modId] = module;
+ }
+
+ int qId = row.QuestionId;
+ var question = module.Questions.FirstOrDefault(q => q.Id == qId);
+ if (question == null)
+ {
+ byte[]? qImage = row.QImage is DBNull ? null : (byte[]?)row.QImage;
+ if (qImage != null && qImage.Length <100) qImage = null;
+ question = new QuestionModel { Id = qId, Image = qImage };
+ module.Questions.Add(question);
+ }
+ question.Text["nl"] = row.QTextNL;
+ question.Text["fr"] = row.QTextFR;
+ question.Text["en"] = row.QTextEN;
+
+ int aId = row.AnswerId;
+ var answer = question.Answers.FirstOrDefault(a => a.Id == aId);
+ if (answer == null)
+ {
+ byte[]? aImage = row.AImage is DBNull ? null : (byte[]?)row.AImage;
+ if (aImage != null && aImage.Length < 100) aImage = null;
+ answer = new AnswerModel { Id = aId, IsCorrect = row.IsCorrect, Image = aImage };
+ question.Answers.Add(answer);
+ }
+ answer.Text["nl"] = row.ATextNL;
+ answer.Text["fr"] = row.ATextFR;
+ answer.Text["en"] = row.ATextEN;
+ }
+
+ return modulesDict.Values.ToList();
+ }
+
+ // --- PDF Generation Logic ---
+ static void GeneratePdfBooklet(List modules, string outputPath)
+ {
+ Document.Create(container =>
+ {
+ //container.WithSettings(settings =>
+ //{
+ // settings.PdfA = false; // Set to true if you need PDF/A compliance
+ //});
+
+ container.Page(page =>
+ {
+ page.Size(PageSizes.A4);
+ page.Margin(15, Unit.Millimetre);
+ page.DefaultTextStyle(x => x.FontFamily("Segoe UI").FontSize(9).FontColor("#1E293B"));
+
+ page.Header().Element(ComposeDocumentHeader);
+
+ page.Content().PaddingVertical(10).Column(column =>
+ {
+ bool isFirstModule = true;
+
+ foreach (var module in modules)
+ {
+ // Avoid inserting a blank page break before the very first cover page
+ if (!isFirstModule)
+ {
+ column.Item().PageBreak();
+ }
+ isFirstModule = false;
+
+ // 1. Render Full-Page Module Cover
+ column.Item().Element(c => ComposeModuleTitlePage(c, module));
+
+ // 2. Start Questions on a Fresh Page Right After Title
+ column.Item().PageBreak();
+
+ // 3. Render Question Cards for this Module
+ column.Item().Column(qCol =>
+ {
+ qCol.Spacing(15);
+ foreach (var question in module.Questions)
+ {
+ qCol.Item().Element(c => ComposeQuestionCard(c, question));
+ }
+ });
+ }
+ });
+
+ page.Footer().Element(ComposeDocumentFooter);
+ });
+ }).WithMetadata(new DocumentMetadata
+ {
+ Title = "Quiz Review Booklet",
+ Author = "LSW-Tech Luc Vandenbroucke",
+ Subject = "Multilingual Quiz Questions and Answers (NL / FR / EN)",
+ Keywords = "Quiz, HPH",
+ Creator = "QuizTools Application"
+ }).GeneratePdf(outputPath);
+ }
+ static void ComposeModuleTitlePage(IContainer container, ModuleModel module)
+ {
+ container.AlignCenter().AlignMiddle().Column(col =>
+ {
+ // Badge Header
+ col.Item().AlignCenter().Background("#1E293B").PaddingHorizontal(12).PaddingVertical(4)
+ .CornerRadius(4).Text($"MODULE {module.Id}").Bold().FontSize(12).FontColor("#FFFFFF");
+
+ // Module Names Stacked
+ col.Item().PaddingTop(20).AlignCenter().Text(module.NameNL)
+ .Bold().FontSize(22).FontColor("#0F172A");
+
+ col.Item().PaddingTop(6).AlignCenter().Text(module.NameFR)
+ .SemiBold().FontSize(18).FontColor("#3B82F6");
+
+ col.Item().PaddingTop(6).AlignCenter().Text(module.NameEN)
+ .Medium().FontSize(16).FontColor("#10B981");
+
+ // Decorative Accent Bar
+ col.Item().PaddingTop(25).Width(100).Height(3).Background("#CBD5E1");
+
+ // Question Counter
+ col.Item().PaddingTop(15).AlignCenter().Text($"{module.Questions.Count} Questions")
+ .FontSize(11).FontColor("#64748B");
+ });
+ }
+
+ // --- Header Component ---
+ static void ComposeDocumentHeader(IContainer container)
+ {
+ container.BorderBottom(1).BorderColor("#E2E8F0").PaddingBottom(5).Row(row =>
+ {
+ row.RelativeItem().Text("Quiz Questions and Answers").Bold().FontSize(10).FontColor("#1E293B");
+ row.AutoItem().Text("23/08/2026").FontSize(10).FontColor("#64748B");
+ });
+ }
+
+ // --- First-Page Title Banner Component ---
+ static void ComposeTitleBanner(IContainer container)
+ {
+ container.Background("#F8FAFC").Border(1).BorderColor("#CBD5E1").CornerRadius(4).Padding(12).Row(row =>
+ {
+ row.RelativeItem().Column(col =>
+ {
+ col.Item().Text("Quiz Questions and Answers").Bold().FontSize(18).FontColor("#0F172A");
+ col.Item().PaddingTop(2).Text("Multilingual Module Review Export (NL / FR / EN)").FontSize(10).FontColor("#64748B");
+ });
+ row.AutoItem().AlignBottom().Text("Date: 23/08/2026").FontSize(10).FontColor("#64748B");
+ });
+ }
+
+ // --- Footer Component ---
+ //static void ComposeDocumentFooter(IContainer container)
+ //{
+ // container.BorderTop(1).BorderColor("#E2E8F0").PaddingTop(5).Row(row =>
+ // {
+ // // Left side: Creator info & date
+ // row.RelativeItem().Text(x =>
+ // {
+ // x.Span("Created by ").FontColor("#64748B");
+ // x.Span("Luc Vandenbroucke").Bold().FontColor("#1E293B");
+ // x.Span(" — 23.08.2026").FontColor("#64748B");
+ // });
+
+ // // Right side: Dynamic Page Numbering
+ // row.AutoItem().Text(x =>
+ // {
+ // x.Span("Page ").FontColor("#64748B");
+ // x.CurrentPageNumber().FontColor("#1E293B").Bold();
+ // x.Span(" of ").FontColor("#64748B");
+ // x.TotalPages().FontColor("#1E293B").Bold();
+ // });
+ // });
+ //}
+
+ static string GetSvgContent(string filePath)
+ {
+ if (File.Exists(filePath))
+ {
+ return File.ReadAllText(filePath);
+ }
+
+ // Fallback minimal SVG dot if file is missing
+ return @"";
+ }
+
+ static void ComposeDocumentFooter(IContainer container)
+ {
+ // Read your SVG string from file or a string variable
+ string mySvg = GetSvgContent("images\\lswtech300-95.svg");
+
+ container.BorderTop(1).BorderColor("#E2E8F0").PaddingTop(5).Row(row =>
+ {
+ // Left side: Render SVG image (constrained in height)
+ row.RelativeItem().Row(leftRow =>
+ {
+ leftRow.AutoItem()
+ .MaxHeight(16) // Keeps the SVG small and aligned with footer text
+ .Svg(mySvg);
+
+ leftRow.AutoItem()
+ .PaddingLeft(6)
+ .AlignMiddle()
+ .Text("— 23.08.2026")
+ .FontSize(8)
+ .FontColor("#64748B");
+ });
+
+ // Right side: Page Numbering
+ row.AutoItem().AlignMiddle().Text(x =>
+ {
+ x.Span("Page ").FontColor("#64748B");
+ x.CurrentPageNumber().FontColor("#1E293B").Bold();
+ x.Span(" of ").FontColor("#64748B");
+ x.TotalPages().FontColor("#1E293B").Bold();
+ });
+ });
+ }
+
+ static void ComposeModuleHeader(IContainer container, string title)
+ {
+ container.BorderBottom(2).BorderColor("#1E293B").PaddingBottom(5).Text(title)
+ .FontSize(16).Bold().FontColor("#1E293B");
+ }
+
+ static void ComposeQuestionCard(IContainer container, QuestionModel q)
+ {
+ // NO .ShowEntire() on the root container so multi-page questions can render
+ container.ShowEntire().Border(1).BorderColor("#CBD5E1").CornerRadius(4).Column(column =>
+ {
+ // 1. Keep the card header together with the first text block if possible
+ column.Item().ShowEntire().Background("#1E293B").Padding(6)
+ .Text($"Question #{q.Id}").Bold().FontColor("#FFFFFF");
+
+ column.Item().Padding(10).Column(body =>
+ {
+ // Question Image
+ if (q.Image != null && q.Image.Length > 0)
+ {
+ body.Item().PaddingVertical(6).AlignCenter().MaxHeight(MaxQuestionImageHeight ).Image(q.Image);
+ }
+
+ // Language Text Blocks
+ ComposeLangBlock(body, "NL", q.Text.GetValueOrDefault("nl"), "#F97316");
+ ComposeLangBlock(body, "FR", q.Text.GetValueOrDefault("fr"), "#3B82F6");
+ ComposeLangBlock(body, "EN", q.Text.GetValueOrDefault("en"), "#10B981");
+
+ // Answers Section
+ body.Item().PaddingTop(8).Text("ANSWERS").Bold().FontSize(8).FontColor("#64748B");
+
+ foreach (var ans in q.Answers)
+ {
+ // Each individual answer option stays intact on one page
+ body.Item().PaddingTop(5).Element(c => ComposeAnswerCard(c, ans));
+ }
+ });
+ });
+ }
+
+ static void ComposeAnswerCard(IContainer container, AnswerModel a)
+ {
+ string borderColor = a.IsCorrect ? "#86EFAC" : "#E2E8F0";
+ string bgColor = a.IsCorrect ? "#F0FDF4" : "#FFFFFF";
+
+ // .ShowEntire() here prevents individual answer choices from splitting mid-text
+ container.ShowEntire().Border(1).BorderColor(borderColor).Background(bgColor).CornerRadius(4).Padding(6).Column(col =>
+ {
+ col.Item().Row(row =>
+ {
+ row.RelativeItem().Text($"Option {a.Id}").Bold();
+ if (a.IsCorrect)
+ {
+ row.AutoItem().Background("#16A34A").PaddingHorizontal(4).PaddingVertical(1)
+ .CornerRadius(2).Text("CORRECT").FontSize(7).Bold().FontColor("#FFFFFF");
+ }
+ });
+
+ if (a.Image != null && a.Image.Length > 0)
+ {
+ col.Item().PaddingVertical(4).AlignCenter().MaxHeight(MaxAnswerImageHeight).Image(a.Image);
+ }
+
+ ComposeLangBlock(col, "NL", a.Text.GetValueOrDefault("nl"), "#F97316");
+ ComposeLangBlock(col, "FR", a.Text.GetValueOrDefault("fr"), "#3B82F6");
+ ComposeLangBlock(col, "EN", a.Text.GetValueOrDefault("en"), "#10B981");
+ });
+ }
+
+ static void ComposeLangBlock(ColumnDescriptor column, string tag, string? text, string accentColor)
+ {
+ if (string.IsNullOrWhiteSpace(text)) return;
+
+ column.Item().PaddingTop(3).BorderLeft(3).BorderColor(accentColor).Background("#F8FAFC").Padding(4).Row(row =>
+ {
+ row.AutoItem().PaddingRight(5).Text(tag).Bold().FontSize(8).FontColor("#64748B");
+ row.RelativeItem().Text(text);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/QuizTools/QuizTools.csproj b/QuizTools/QuizTools.csproj
new file mode 100644
index 0000000..518c3aa
--- /dev/null
+++ b/QuizTools/QuizTools.csproj
@@ -0,0 +1,26 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
+
+
+
+
+
+
diff --git a/QuizTools/images/lswtech300-95.svg b/QuizTools/images/lswtech300-95.svg
new file mode 100644
index 0000000..2c9c69d
--- /dev/null
+++ b/QuizTools/images/lswtech300-95.svg
@@ -0,0 +1,130 @@
+
+
+
+