using System.Text.Json.Serialization; namespace LehrerApp.Templating; public enum PlaceholderType { Text, Multiline, Date, Number, Image, Table, Chart, Drawing } public abstract record PlaceholderValue(PlaceholderType Type); public sealed record TextValue(string Value) : PlaceholderValue(PlaceholderType.Text); public sealed record MultilineValue(string Value) : PlaceholderValue(PlaceholderType.Multiline); public sealed record DateValue(DateOnly Value) : PlaceholderValue(PlaceholderType.Date); public sealed record NumberValue(decimal Value) : PlaceholderValue(PlaceholderType.Number); public sealed record ImageValue(byte[] Data, string MimeType) : PlaceholderValue(PlaceholderType.Image); public sealed record ImageMetadata(int Width, int Height); public sealed record TableValue(IReadOnlyList Columns, IReadOnlyList> Rows) : PlaceholderValue(PlaceholderType.Table); public sealed record ChartPoint(string X, decimal Y); public sealed record ChartSeries(string Label, IReadOnlyList Points); public sealed record ChartValue(IReadOnlyList Series) : PlaceholderValue(PlaceholderType.Chart); public abstract record DrawingCommand; public sealed record DrawString(float X, float Y, string Text, float FontSize = 11, string Color = "#000000", bool Bold = false, bool Italic = false, string FontFamily = "") : DrawingCommand; public enum DrawingTextAlignment { AlignLeft, AlignCenter, AlignRight } public sealed record DrawStringEx(float X, float Y, float Height, float Width, string Text, DrawingTextAlignment Alignment = DrawingTextAlignment.AlignLeft, float FontSize = 11, string FontFamily = "", string Color = "#000000", bool Bold = false, bool Italic = false) : DrawingCommand; public sealed record MoveTo(float X, float Y) : DrawingCommand; public sealed record LineTo(float X, float Y, string Color = "#000000", float StrokeWidth = 1) : DrawingCommand; public sealed record DrawLine(float X1, float Y1, float X2, float Y2, string Color = "#000000", float StrokeWidth = 1) : DrawingCommand; public sealed record DrawRectangle(float X, float Y, float Width, float Height, string StrokeColor = "#000000", float StrokeWidth = 1, string FillColor = "none") : DrawingCommand; public sealed record DrawRoundedRectangle(float X, float Y, float Width, float Height, float CornerRadius, string StrokeColor = "#000000", float StrokeWidth = 1, string FillColor = "none") : DrawingCommand; public sealed record DrawCircle(float CenterX, float CenterY, float Radius, string StrokeColor = "#000000", float StrokeWidth = 1, string FillColor = "none") : DrawingCommand; public sealed record DrawImage(float X, float Y, float Width, float Height, byte[] Data, string MimeType) : DrawingCommand; /// Declarative drawing supplied by an external data provider. Coordinates use the template layout unit; font sizes use points. public sealed record DrawingValue(IReadOnlyList Commands, float ContentHeight) : PlaceholderValue(PlaceholderType.Drawing); /// A recorded drawing surface handed to an in-process external renderer. public sealed class DrawingCanvas { private readonly List _commands = []; public IReadOnlyList Commands => _commands; public void DrawString(float x, float y, string text, float fontSize = 11, string color = "#000000", bool bold = false, bool italic = false, string fontFamily = "") => _commands.Add(new LehrerApp.Templating.DrawString(x, y, text, fontSize, color, bold, italic, fontFamily)); public void DrawStringEx(float x, float y, float height, float width, string text, DrawingTextAlignment alignment = DrawingTextAlignment.AlignLeft, float fontSize = 11, string fontFamily = "", string color = "#000000", bool bold = false, bool italic = false) => _commands.Add(new LehrerApp.Templating.DrawStringEx(x, y, height, width, text, alignment, fontSize, fontFamily, color, bold, italic)); public void MoveTo(float x, float y) => _commands.Add(new LehrerApp.Templating.MoveTo(x, y)); public void LineTo(float x, float y, string color = "#000000", float strokeWidth = 1) => _commands.Add(new LehrerApp.Templating.LineTo(x, y, color, strokeWidth)); public void DrawLine(float x1, float y1, float x2, float y2, string color = "#000000", float strokeWidth = 1) => _commands.Add(new LehrerApp.Templating.DrawLine(x1, y1, x2, y2, color, strokeWidth)); public void DrawRectangle(float x, float y, float width, float height, string strokeColor = "#000000", float strokeWidth = 1, string fillColor = "none") => _commands.Add(new LehrerApp.Templating.DrawRectangle(x, y, width, height, strokeColor, strokeWidth, fillColor)); public void DrawRoundedRectangle(float x, float y, float width, float height, float cornerRadius, string strokeColor = "#000000", float strokeWidth = 1, string fillColor = "none") => _commands.Add(new LehrerApp.Templating.DrawRoundedRectangle(x, y, width, height, cornerRadius, strokeColor, strokeWidth, fillColor)); public void DrawCircle(float centerX, float centerY, float radius, string strokeColor = "#000000", float strokeWidth = 1, string fillColor = "none") => _commands.Add(new LehrerApp.Templating.DrawCircle(centerX, centerY, radius, strokeColor, strokeWidth, fillColor)); public void DrawImage(float x, float y, float width, float height, byte[] data, string mimeType) => _commands.Add(new LehrerApp.Templating.DrawImage(x, y, width, height, data, mimeType)); } public sealed class DrawingPageContext(float width, float height, int pageNumber, DrawingCanvas canvas, object? state) { public float Width { get; } = width; public float Height { get; } = height; public int PageNumber { get; } = pageNumber; public DrawingCanvas Canvas { get; } = canvas; public object? State { get; set; } = state; } public delegate bool DrawingPageCallback(DrawingPageContext context); /// /// In-process callback drawing. The callback returns true when finished or false to request another box. /// State is carried across callbacks. It is recorded before QuestPDF performs layout and is never executed by a package. /// public sealed record PagedDrawingValue(DrawingPageCallback DrawPage, object? InitialState = null, int MaxPages = 1_000) : PlaceholderValue(PlaceholderType.Drawing); public interface ITemplateDataProvider { IReadOnlyDictionary GetValues(); } public sealed record PageSizeDefinition(float Width, float Height, string Unit = "mm"); public sealed record PlaceholderDefinition(string Name, PlaceholderType Type, bool Required = false, bool IsConstant = false, string? ConstantValue = null, bool Bold = false, bool Italic = false, bool Underline = false); public sealed class TemplateManifest { public int SchemaVersion { get; set; } = 1; public string Id { get; set; } = "neue-vorlage"; public string Name { get; set; } = "Neue Vorlage"; public string Description { get; set; } = ""; public PageSizeDefinition PageSize { get; set; } = new(210, 297); public string LayoutFile { get; set; } = "layout.tpl"; public string? ContinuationLayoutFile { get; set; } public string MetadataFile { get; set; } = TemplateMetadataText.FileName; [JsonIgnore] public Dictionary Metadata { get; set; } = new(StringComparer.OrdinalIgnoreCase); public List Placeholders { get; set; } = []; } public abstract record TemplateElement(int Line, float X, float Y, float Width, float Height, IReadOnlyDictionary Attributes); public sealed record BackgroundElement(int Line, string Path) : TemplateElement(Line, 0, 0, 0, 0, EmptyAttributes.Value); public sealed record ImageElement(int Line, string Path, float X, float Y, float Width, float Height, IReadOnlyDictionary Attributes) : TemplateElement(Line, X, Y, Width, Height, Attributes); public sealed record TextElement(int Line, float X, float Y, string Content, string? Placeholder, string? Format, IReadOnlyDictionary Attributes) : TemplateElement(Line, X, Y, 0, 0, Attributes); public sealed record TextBoxElement(int Line, float X, float Y, float Width, float Height, string Content, string? Placeholder, string? Format, IReadOnlyDictionary Attributes) : TemplateElement(Line, X, Y, Width, Height, Attributes); public sealed record FlowBoxElement(int Line, float X, float Y, float Width, float Height, string Content, string? Placeholder, string? Format, IReadOnlyDictionary Attributes) : TemplateElement(Line, X, Y, Width, Height, Attributes); public sealed record DrawBoxElement(int Line, float X, float Y, float Width, float Height, string Placeholder, IReadOnlyDictionary Attributes) : TemplateElement(Line, X, Y, Width, Height, Attributes); public sealed record FlowDrawBoxElement(int Line, float X, float Y, float Width, float Height, string Placeholder, IReadOnlyDictionary Attributes) : TemplateElement(Line, X, Y, Width, Height, Attributes); public sealed record TableElement(int Line, float X, float Y, float Width, float Height, string Placeholder, IReadOnlyDictionary Attributes) : TemplateElement(Line, X, Y, Width, Height, Attributes); public sealed record ChartElement(int Line, float X, float Y, float Width, float Height, string Placeholder, string ChartType, IReadOnlyDictionary Attributes) : TemplateElement(Line, X, Y, Width, Height, Attributes); public sealed record FlowSlotDefinition(int Line, string Name, float X, float Y, float Width, float Height); public sealed record PageTemplateDefinition(string Name, IReadOnlyList Elements, IReadOnlyList FlowSlots); public sealed record ContentFlowDefinition(string Name, IReadOnlyList Elements); internal static class EmptyAttributes { public static readonly IReadOnlyDictionary Value = new Dictionary(); } public sealed record TemplateLayout(float Width, float Height, string Unit, IReadOnlyList Elements) { public int FormatVersion { get; init; } = 1; public IReadOnlyList PageTemplates { get; init; } = []; public IReadOnlyList ContentFlows { get; init; } = []; public bool UsesPageTemplates => PageTemplates.Count > 0 || ContentFlows.Count > 0; } public sealed record LoadedTemplate(TemplateManifest Manifest, TemplateLayout Layout, IReadOnlyDictionary Assets, string SourceName = "", TemplateLayout? ContinuationLayout = null); public enum ValidationSeverity { Warning, Error } public sealed record ValidationIssue(ValidationSeverity Severity, string Message, int? Line = null); public sealed record ValidationResult(IReadOnlyList Issues) { public bool IsValid => Issues.All(x => x.Severity != ValidationSeverity.Error); public static ValidationResult Success { get; } = new([]); } public sealed class TemplateValidationException(ValidationResult result) : Exception(string.Join(Environment.NewLine, result.Issues.Select(x => x.Message))) { public ValidationResult Result { get; } = result; } public interface ITemplateRenderer { byte[] RenderToPdf(LoadedTemplate template, ITemplateDataProvider data); } public interface ITemplatePreviewRenderer { byte[] RenderFirstPageToPng(LoadedTemplate template, ITemplateDataProvider data, int dpi = 120); } public interface ITemplateLoader { LoadedTemplate LoadFromPackage(string packagePath); LoadedTemplate LoadFromPackage(Stream package, string sourceName = ""); ValidationResult Validate(LoadedTemplate template, IReadOnlyDictionary providedTypes); } public interface ITemplateMigrator { int SourceVersion { get; } TemplateManifest Migrate(TemplateManifest manifest); }