TemplateDesigner: Redesign des TemplateSkripts
This commit is contained in:
@@ -9,6 +9,12 @@ public sealed class LayoutParser
|
||||
{
|
||||
var issues = new List<ValidationIssue>();
|
||||
var elements = new List<TemplateElement>();
|
||||
var pageTemplates = new List<PageTemplateDefinition>();
|
||||
var contentFlows = new List<ContentFlowDefinition>();
|
||||
string? currentPageName = null, currentFlowName = null;
|
||||
List<TemplateElement>? currentPageElements = null, currentFlowElements = null;
|
||||
List<FlowSlotDefinition>? currentSlots = null;
|
||||
var formatVersion = 1;
|
||||
float width = 0, height = 0;
|
||||
var unit = "mm";
|
||||
var pageSeen = false;
|
||||
@@ -18,15 +24,75 @@ public sealed class LayoutParser
|
||||
{
|
||||
var lineNumber = index + 1;
|
||||
var raw = lines[index].Trim();
|
||||
if (raw.Length == 0 || raw.StartsWith('#')) continue;
|
||||
if (raw.Length == 0) continue;
|
||||
try
|
||||
{
|
||||
if (raw.StartsWith("#pragma", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var pragma = Tokenize(raw);
|
||||
Require(pragma, 2);
|
||||
switch (pragma[1].ToLowerInvariant())
|
||||
{
|
||||
case "format-version":
|
||||
Require(pragma, 3);
|
||||
if (!int.TryParse(pragma[2], out formatVersion) || formatVersion is < 1 or > 3)
|
||||
throw new FormatException("format-version muss zwischen 1 und 3 liegen.");
|
||||
break;
|
||||
case "page-template":
|
||||
Require(pragma, 3);
|
||||
if (currentPageName is not null || currentFlowName is not null)
|
||||
throw new FormatException("Verschachtelte Bereiche sind nicht erlaubt.");
|
||||
currentPageName = pragma[2]; currentPageElements = []; currentSlots = [];
|
||||
break;
|
||||
case "end-page-template":
|
||||
if (currentPageName is null || currentPageElements is null || currentSlots is null)
|
||||
throw new FormatException("Kein page-template ist geöffnet.");
|
||||
if (pageTemplates.Any(x => x.Name.Equals(currentPageName, StringComparison.OrdinalIgnoreCase)))
|
||||
throw new FormatException($"page-template „{currentPageName}“ ist mehrfach definiert.");
|
||||
pageTemplates.Add(new(currentPageName, currentPageElements, currentSlots));
|
||||
currentPageName = null; currentPageElements = null; currentSlots = null;
|
||||
break;
|
||||
case "flow-slot":
|
||||
Require(pragma, 3);
|
||||
if (currentPageName is null || currentSlots is null)
|
||||
throw new FormatException("flow-slot muss innerhalb eines page-template stehen.");
|
||||
var slotAttributes = Attributes(pragma, 3);
|
||||
foreach (var required in new[] { "x", "y", "w", "h" })
|
||||
if (!slotAttributes.ContainsKey(required))
|
||||
throw new FormatException($"flow-slot benötigt {required}=…");
|
||||
var slot = new FlowSlotDefinition(lineNumber, pragma[2], Number(slotAttributes["x"]),
|
||||
Number(slotAttributes["y"]), Number(slotAttributes["w"]), Number(slotAttributes["h"]));
|
||||
if (slot.Width <= 0 || slot.Height <= 0) throw new FormatException("Flow-Slot muss eine positive Größe haben.");
|
||||
if (currentSlots.Any(x => x.Name.Equals(slot.Name, StringComparison.OrdinalIgnoreCase)))
|
||||
throw new FormatException($"flow-slot „{slot.Name}“ ist in dieser Seitenvorlage mehrfach definiert.");
|
||||
currentSlots.Add(slot);
|
||||
break;
|
||||
case "content-flow":
|
||||
Require(pragma, 3);
|
||||
if (currentPageName is not null || currentFlowName is not null)
|
||||
throw new FormatException("Verschachtelte Bereiche sind nicht erlaubt.");
|
||||
currentFlowName = pragma[2]; currentFlowElements = [];
|
||||
break;
|
||||
case "end-content-flow":
|
||||
if (currentFlowName is null || currentFlowElements is null)
|
||||
throw new FormatException("Kein content-flow ist geöffnet.");
|
||||
if (contentFlows.Any(x => x.Name.Equals(currentFlowName, StringComparison.OrdinalIgnoreCase)))
|
||||
throw new FormatException($"content-flow „{currentFlowName}“ ist mehrfach definiert.");
|
||||
contentFlows.Add(new(currentFlowName, currentFlowElements));
|
||||
currentFlowName = null; currentFlowElements = null;
|
||||
break;
|
||||
default: throw new FormatException($"Unbekanntes Pragma „{pragma[1]}“.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (raw.StartsWith('#')) continue;
|
||||
var tokens = Tokenize(raw);
|
||||
if (tokens.Count == 0) continue;
|
||||
var keyword = tokens[0].ToUpperInvariant();
|
||||
if (!pageSeen && keyword != "PAGE")
|
||||
throw new FormatException("PAGE muss das erste Statement sein.");
|
||||
|
||||
TemplateElement? parsedElement = null;
|
||||
switch (keyword)
|
||||
{
|
||||
case "PAGE":
|
||||
@@ -39,40 +105,64 @@ public sealed class LayoutParser
|
||||
pageSeen = true;
|
||||
break;
|
||||
case "BG":
|
||||
Require(tokens, 2); elements.Add(new BackgroundElement(lineNumber, tokens[1])); break;
|
||||
if (currentFlowName is not null) throw new FormatException("BG ist in einem content-flow nicht erlaubt.");
|
||||
Require(tokens, 2); parsedElement = new BackgroundElement(lineNumber, tokens[1]); break;
|
||||
case "IMG":
|
||||
Require(tokens, 6);
|
||||
var imageAttributes = Attributes(tokens, 6);
|
||||
var flowImage = currentFlowName is not null;
|
||||
Require(tokens, flowImage ? 2 : 6);
|
||||
var imageAttributes = Attributes(tokens, flowImage ? 2 : 6);
|
||||
if (imageAttributes.TryGetValue("scale", out var scale)) Percentage(scale);
|
||||
elements.Add(new ImageElement(lineNumber, tokens[1],
|
||||
Number(tokens[2]), Number(tokens[3]), Number(tokens[4]), Number(tokens[5]), imageAttributes));
|
||||
parsedElement = flowImage
|
||||
? new ImageElement(lineNumber, tokens[1], 0, 0,
|
||||
OptionalNumber(imageAttributes, "w"), OptionalNumber(imageAttributes, "h"), imageAttributes)
|
||||
: new ImageElement(lineNumber, tokens[1], Number(tokens[2]), Number(tokens[3]),
|
||||
Number(tokens[4]), Number(tokens[5]), imageAttributes);
|
||||
break;
|
||||
case "TEXT":
|
||||
Require(tokens, 4);
|
||||
var textRef = Reference(tokens[3]);
|
||||
elements.Add(new TextElement(lineNumber, Number(tokens[1]), Number(tokens[2]),
|
||||
tokens[3], textRef.Name, textRef.Format, Attributes(tokens, 4))); break;
|
||||
var flowText = currentFlowName is not null;
|
||||
Require(tokens, flowText ? 2 : 4);
|
||||
var textContent = tokens[flowText ? 1 : 3];
|
||||
var textRef = Reference(textContent);
|
||||
parsedElement = new TextElement(lineNumber, flowText ? 0 : Number(tokens[1]),
|
||||
flowText ? 0 : Number(tokens[2]), textContent, textRef.Name, textRef.Format,
|
||||
Attributes(tokens, flowText ? 2 : 4)); break;
|
||||
case "TEXTBOX":
|
||||
Require(tokens, 6);
|
||||
var boxRef = Reference(tokens[5]);
|
||||
elements.Add(new TextBoxElement(lineNumber, Number(tokens[1]), Number(tokens[2]),
|
||||
Number(tokens[3]), Number(tokens[4]), tokens[5], boxRef.Name, boxRef.Format,
|
||||
Attributes(tokens, 6))); break;
|
||||
var flowBox = currentFlowName is not null;
|
||||
Require(tokens, flowBox ? 2 : 6);
|
||||
var boxContent = tokens[flowBox ? 1 : 5];
|
||||
var boxRef = Reference(boxContent);
|
||||
parsedElement = new TextBoxElement(lineNumber, flowBox ? 0 : Number(tokens[1]),
|
||||
flowBox ? 0 : Number(tokens[2]), flowBox ? 0 : Number(tokens[3]),
|
||||
flowBox ? 0 : Number(tokens[4]), boxContent, boxRef.Name, boxRef.Format,
|
||||
Attributes(tokens, flowBox ? 2 : 6)); break;
|
||||
case "TABLE":
|
||||
Require(tokens, 6);
|
||||
elements.Add(new TableElement(lineNumber, Number(tokens[1]), Number(tokens[2]),
|
||||
Number(tokens[3]), Number(tokens[4]), RequiredReference(tokens[5]), Attributes(tokens, 6)));
|
||||
var flowTable = currentFlowName is not null;
|
||||
Require(tokens, flowTable ? 2 : 6);
|
||||
parsedElement = new TableElement(lineNumber, flowTable ? 0 : Number(tokens[1]),
|
||||
flowTable ? 0 : Number(tokens[2]), flowTable ? 0 : Number(tokens[3]),
|
||||
flowTable ? 0 : Number(tokens[4]), RequiredReference(tokens[flowTable ? 1 : 5]),
|
||||
Attributes(tokens, flowTable ? 2 : 6));
|
||||
break;
|
||||
case "CHART":
|
||||
Require(tokens, 6);
|
||||
var attributes = Attributes(tokens, 6);
|
||||
var flowChart = currentFlowName is not null;
|
||||
Require(tokens, flowChart ? 2 : 6);
|
||||
var attributes = Attributes(tokens, flowChart ? 2 : 6);
|
||||
var chartType = attributes.GetValueOrDefault("type", "bar").ToLowerInvariant();
|
||||
if (chartType is not ("line" or "bar")) throw new FormatException("CHART type muss line oder bar sein.");
|
||||
elements.Add(new ChartElement(lineNumber, Number(tokens[1]), Number(tokens[2]),
|
||||
Number(tokens[3]), Number(tokens[4]), RequiredReference(tokens[5]), chartType, attributes));
|
||||
parsedElement = new ChartElement(lineNumber, flowChart ? 0 : Number(tokens[1]),
|
||||
flowChart ? 0 : Number(tokens[2]), flowChart ? 0 : Number(tokens[3]),
|
||||
flowChart ? 0 : Number(tokens[4]), RequiredReference(tokens[flowChart ? 1 : 5]), chartType, attributes);
|
||||
break;
|
||||
default: throw new FormatException($"Unbekanntes Element „{tokens[0]}“.");
|
||||
}
|
||||
if (parsedElement is not null)
|
||||
{
|
||||
elements.Add(parsedElement);
|
||||
if (currentPageElements is not null) currentPageElements.Add(parsedElement);
|
||||
else if (currentFlowElements is not null) currentFlowElements.Add(parsedElement);
|
||||
else if (pageTemplates.Count > 0 || contentFlows.Count > 0 || formatVersion >= 3)
|
||||
throw new FormatException("Elemente müssen in page-template oder content-flow stehen.");
|
||||
}
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
@@ -81,8 +171,32 @@ public sealed class LayoutParser
|
||||
}
|
||||
|
||||
if (!pageSeen) issues.Add(new(ValidationSeverity.Error, "PAGE fehlt."));
|
||||
if (currentPageName is not null) issues.Add(new(ValidationSeverity.Error, $"page-template „{currentPageName}“ wurde nicht geschlossen."));
|
||||
if (currentFlowName is not null) issues.Add(new(ValidationSeverity.Error, $"content-flow „{currentFlowName}“ wurde nicht geschlossen."));
|
||||
if (pageTemplates.Count > 0 || contentFlows.Count > 0)
|
||||
{
|
||||
var firstTemplate = pageTemplates.FirstOrDefault(x => x.Name.Equals("first", StringComparison.OrdinalIgnoreCase));
|
||||
var continuationTemplate = pageTemplates.FirstOrDefault(x => x.Name.Equals("continuation", StringComparison.OrdinalIgnoreCase));
|
||||
if (firstTemplate is null)
|
||||
issues.Add(new(ValidationSeverity.Error, "Die Seitenvorlage „first“ fehlt."));
|
||||
foreach (var flow in contentFlows)
|
||||
{
|
||||
if (firstTemplate is not null && !firstTemplate.FlowSlots.Any(slot => slot.Name.Equals(flow.Name, StringComparison.OrdinalIgnoreCase)))
|
||||
issues.Add(new(ValidationSeverity.Error, $"Für content-flow „{flow.Name}“ fehlt der gleichnamige flow-slot im Seitentyp „first“."));
|
||||
if (continuationTemplate is not null && !continuationTemplate.FlowSlots.Any(slot => slot.Name.Equals(flow.Name, StringComparison.OrdinalIgnoreCase)))
|
||||
issues.Add(new(ValidationSeverity.Error, $"Für content-flow „{flow.Name}“ fehlt der gleichnamige flow-slot im Seitentyp „continuation“."));
|
||||
}
|
||||
foreach (var slot in pageTemplates.SelectMany(page => page.FlowSlots))
|
||||
if (slot.X < 0 || slot.Y < 0 || slot.X + slot.Width > width || slot.Y + slot.Height > height)
|
||||
issues.Add(new(ValidationSeverity.Error, $"Flow-Slot „{slot.Name}“ in Zeile {slot.Line} liegt außerhalb der Seite.", slot.Line));
|
||||
}
|
||||
if (issues.Count > 0) throw new TemplateValidationException(new(issues));
|
||||
return new(width, height, unit, elements);
|
||||
return new(width, height, unit, elements)
|
||||
{
|
||||
FormatVersion = formatVersion,
|
||||
PageTemplates = pageTemplates,
|
||||
ContentFlows = contentFlows,
|
||||
};
|
||||
}
|
||||
|
||||
private static (string? Name, string? Format) Reference(string value)
|
||||
@@ -99,6 +213,9 @@ public sealed class LayoutParser
|
||||
private static float Number(string value) => float.TryParse(value, NumberStyles.Float,
|
||||
CultureInfo.InvariantCulture, out var result) ? result : throw new FormatException($"„{value}“ ist keine Zahl.");
|
||||
|
||||
private static float OptionalNumber(IReadOnlyDictionary<string, string> attributes, string name) =>
|
||||
attributes.TryGetValue(name, out var value) ? Number(value) : 0;
|
||||
|
||||
public static float Percentage(string value)
|
||||
{
|
||||
var normalized = value.EndsWith('%') ? value[..^1] : value;
|
||||
|
||||
@@ -61,6 +61,11 @@ public sealed record ChartElement(int Line, float X, float Y, float Width, float
|
||||
string Placeholder, string ChartType, IReadOnlyDictionary<string, string> 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<TemplateElement> Elements,
|
||||
IReadOnlyList<FlowSlotDefinition> FlowSlots);
|
||||
public sealed record ContentFlowDefinition(string Name, IReadOnlyList<TemplateElement> Elements);
|
||||
|
||||
internal static class EmptyAttributes
|
||||
{
|
||||
public static readonly IReadOnlyDictionary<string, string> Value =
|
||||
@@ -68,7 +73,13 @@ internal static class EmptyAttributes
|
||||
}
|
||||
|
||||
public sealed record TemplateLayout(float Width, float Height, string Unit,
|
||||
IReadOnlyList<TemplateElement> Elements);
|
||||
IReadOnlyList<TemplateElement> Elements)
|
||||
{
|
||||
public int FormatVersion { get; init; } = 1;
|
||||
public IReadOnlyList<PageTemplateDefinition> PageTemplates { get; init; } = [];
|
||||
public IReadOnlyList<ContentFlowDefinition> ContentFlows { get; init; } = [];
|
||||
public bool UsesPageTemplates => PageTemplates.Count > 0 || ContentFlows.Count > 0;
|
||||
}
|
||||
|
||||
public sealed record LoadedTemplate(TemplateManifest Manifest, TemplateLayout Layout,
|
||||
IReadOnlyDictionary<string, byte[]> Assets, string SourceName = "");
|
||||
|
||||
@@ -15,9 +15,14 @@ public sealed class QuestTemplateRenderer : ITemplateRenderer, ITemplatePreviewR
|
||||
BuildDocument(template, ValidateData(template, data)).GeneratePdf();
|
||||
|
||||
public byte[] RenderFirstPageToPng(LoadedTemplate template, ITemplateDataProvider data, int dpi = 120)
|
||||
{
|
||||
return RenderPagesToPng(template, data, dpi).First();
|
||||
}
|
||||
|
||||
public IReadOnlyList<byte[]> RenderPagesToPng(LoadedTemplate template, ITemplateDataProvider data, int dpi = 120)
|
||||
{
|
||||
var settings = new ImageGenerationSettings { ImageFormat = ImageFormat.Png, RasterDpi = dpi };
|
||||
return BuildDocument(template, ValidateData(template, data)).GenerateImages(settings).First();
|
||||
return BuildDocument(template, ValidateData(template, data)).GenerateImages(settings).ToList();
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, PlaceholderValue> ValidateData(LoadedTemplate template,
|
||||
@@ -30,6 +35,11 @@ public sealed class QuestTemplateRenderer : ITemplateRenderer, ITemplatePreviewR
|
||||
}
|
||||
|
||||
private static IDocument BuildDocument(LoadedTemplate template,
|
||||
IReadOnlyDictionary<string, PlaceholderValue> values) => template.Layout.UsesPageTemplates
|
||||
? BuildFlowDocument(template, values)
|
||||
: BuildLegacyDocument(template, values);
|
||||
|
||||
private static IDocument BuildLegacyDocument(LoadedTemplate template,
|
||||
IReadOnlyDictionary<string, PlaceholderValue> values) => Document.Create(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
@@ -50,6 +60,147 @@ public sealed class QuestTemplateRenderer : ITemplateRenderer, ITemplatePreviewR
|
||||
});
|
||||
});
|
||||
|
||||
private static IDocument BuildFlowDocument(LoadedTemplate template,
|
||||
IReadOnlyDictionary<string, PlaceholderValue> values)
|
||||
{
|
||||
var firstPage = template.Layout.PageTemplates.First(x =>
|
||||
x.Name.Equals("first", StringComparison.OrdinalIgnoreCase));
|
||||
var continuationPage = template.Layout.PageTemplates.FirstOrDefault(x =>
|
||||
x.Name.Equals("continuation", StringComparison.OrdinalIgnoreCase)) ?? firstPage;
|
||||
var populatedFlows = template.Layout.ContentFlows.Where(x => x.Elements.Count > 0).ToList();
|
||||
if (populatedFlows.Count > 1)
|
||||
throw new InvalidDataException("Aktuell darf genau ein Content-Flow Inhalt enthalten. Weitere Flow-Slots können bereits gestaltet werden, parallele paginierende Flows folgen in einer späteren Formatstufe.");
|
||||
var primaryFlow = populatedFlows.FirstOrDefault() ?? template.Layout.ContentFlows.FirstOrDefault();
|
||||
|
||||
return Document.Create(document =>
|
||||
{
|
||||
document.Page(page =>
|
||||
{
|
||||
var pageWidth = UnitConverter.Points(template.Layout.Width, template.Layout.Unit);
|
||||
var pageHeight = UnitConverter.Points(template.Layout.Height, template.Layout.Unit);
|
||||
page.Size(pageWidth, pageHeight);
|
||||
page.Margin(0);
|
||||
ConfigurePageCanvas(page.Background(), firstPage, continuationPage, template, values);
|
||||
|
||||
if (primaryFlow is null)
|
||||
{
|
||||
page.Content().Height(1);
|
||||
return;
|
||||
}
|
||||
|
||||
var firstSlot = FindSlot(firstPage, primaryFlow.Name);
|
||||
var continuationSlot = FindSlot(continuationPage, primaryFlow.Name) ?? firstSlot;
|
||||
if (firstSlot is null)
|
||||
throw new InvalidDataException($"Für content-flow „{primaryFlow.Name}“ fehlt ein flow-slot auf der ersten Seite.");
|
||||
|
||||
if (Math.Abs(firstSlot.X - continuationSlot!.X) > 0.01f ||
|
||||
Math.Abs(firstSlot.Width - continuationSlot.Width) > 0.01f)
|
||||
throw new InvalidDataException($"Der primäre Flow „{primaryFlow.Name}“ muss vorerst auf erster und Folgeseite dieselbe X-Position und Breite besitzen.");
|
||||
|
||||
var left = UnitConverter.Points(firstSlot.X, template.Layout.Unit);
|
||||
var right = UnitConverter.Points(template.Layout.Width - firstSlot.X - firstSlot.Width, template.Layout.Unit);
|
||||
page.MarginLeft(left);
|
||||
page.MarginRight(right);
|
||||
ConfigureVariableVerticalSlot(page, firstSlot, continuationSlot, template.Layout);
|
||||
page.Content().Column(column => RenderFlowColumn(column, primaryFlow.Elements, template, values));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static void ConfigureVariableVerticalSlot(PageDescriptor page, FlowSlotDefinition first,
|
||||
FlowSlotDefinition continuation, TemplateLayout layout)
|
||||
{
|
||||
var firstTop = UnitConverter.Points(first.Y, layout.Unit);
|
||||
var continuationTop = UnitConverter.Points(continuation.Y, layout.Unit);
|
||||
var firstBottom = UnitConverter.Points(layout.Height - first.Y - first.Height, layout.Unit);
|
||||
var continuationBottom = UnitConverter.Points(layout.Height - continuation.Y - continuation.Height, layout.Unit);
|
||||
page.Header().Column(column =>
|
||||
{
|
||||
column.Item().ShowOnce().Height(firstTop);
|
||||
column.Item().SkipOnce().Height(continuationTop);
|
||||
});
|
||||
page.Footer().Column(column =>
|
||||
{
|
||||
column.Item().ShowOnce().Height(firstBottom);
|
||||
column.Item().SkipOnce().Height(continuationBottom);
|
||||
});
|
||||
}
|
||||
|
||||
private static FlowSlotDefinition? FindSlot(PageTemplateDefinition page, string name) =>
|
||||
page.FlowSlots.FirstOrDefault(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
private static void ConfigurePageCanvas(IContainer canvas, PageTemplateDefinition first,
|
||||
PageTemplateDefinition continuation, LoadedTemplate template,
|
||||
IReadOnlyDictionary<string, PlaceholderValue> values)
|
||||
{
|
||||
canvas.Layers(layers =>
|
||||
{
|
||||
layers.PrimaryLayer().Background(Colors.White);
|
||||
foreach (var element in first.Elements)
|
||||
{
|
||||
var current = element;
|
||||
layers.Layer().ShowOnce().Element(root => RenderElement(root, current, template, values));
|
||||
}
|
||||
foreach (var element in continuation.Elements)
|
||||
{
|
||||
var current = element;
|
||||
layers.Layer().SkipOnce().Element(root => RenderElement(root, current, template, values));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void RenderFlowColumn(ColumnDescriptor column, IReadOnlyList<TemplateElement> elements,
|
||||
LoadedTemplate template, IReadOnlyDictionary<string, PlaceholderValue> values)
|
||||
{
|
||||
for (var index = 0; index < elements.Count; index++)
|
||||
{
|
||||
var element = elements[index];
|
||||
var gap = UnitConverter.Points(ParseFloat(element.Attributes, "gap", 0), template.Layout.Unit);
|
||||
var keepWithNext = ParseBool(element.Attributes, "keep-with-next") && index + 1 < elements.Count;
|
||||
if (keepWithNext)
|
||||
{
|
||||
var next = elements[++index];
|
||||
column.Item().PaddingTop(gap).PreventPageBreak().Column(group =>
|
||||
{
|
||||
group.Item().Element(container => RenderFlowElement(container, element, template, values));
|
||||
var nextGap = UnitConverter.Points(ParseFloat(next.Attributes, "gap", 0), template.Layout.Unit);
|
||||
group.Item().PaddingTop(nextGap).Element(container => RenderFlowElement(container, next, template, values));
|
||||
});
|
||||
}
|
||||
else
|
||||
column.Item().PaddingTop(gap).Element(container => RenderFlowElement(container, element, template, values));
|
||||
}
|
||||
}
|
||||
|
||||
private static void RenderFlowElement(IContainer container, TemplateElement element, LoadedTemplate template,
|
||||
IReadOnlyDictionary<string, PlaceholderValue> values)
|
||||
{
|
||||
switch (element)
|
||||
{
|
||||
case TextElement text:
|
||||
RenderResolvedText(container, text.Content, text.Placeholder, text.Format,
|
||||
values, template.Manifest, text.Attributes);
|
||||
break;
|
||||
case TextBoxElement box:
|
||||
RenderResolvedText(container, box.Content, box.Placeholder, box.Format,
|
||||
values, template.Manifest, box.Attributes);
|
||||
break;
|
||||
case ImageElement image:
|
||||
var imageContainer = container;
|
||||
if (image.Width > 0) imageContainer = imageContainer.Width(UnitConverter.Points(image.Width, template.Layout.Unit));
|
||||
if (image.Height > 0) imageContainer = imageContainer.Height(UnitConverter.Points(image.Height, template.Layout.Unit));
|
||||
imageContainer.Image(GetAsset(template, image.Path)).FitArea();
|
||||
break;
|
||||
case TableElement table when values.GetValueOrDefault(table.Placeholder) is TableValue tableValue:
|
||||
TableElementRenderer.Render(container, tableValue, table.Attributes);
|
||||
break;
|
||||
case ChartElement chart when values.GetValueOrDefault(chart.Placeholder) is ChartValue chartValue:
|
||||
var chartHeight = UnitConverter.Points(ParseFloat(chart.Attributes, "h", 50), template.Layout.Unit);
|
||||
ChartElementRenderer.Render(container.Height(chartHeight), chartValue, chart.ChartType, chart.Attributes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RenderElement(IContainer root, TemplateElement element, LoadedTemplate template,
|
||||
IReadOnlyDictionary<string, PlaceholderValue> values)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user