This commit is contained in:
@@ -103,6 +103,7 @@ public sealed class LayoutParser
|
||||
|
||||
private static (string? Name, string? Format) Reference(string value)
|
||||
{
|
||||
if (SystemVariables.IsStandalone(value)) return (null, null);
|
||||
if (!value.StartsWith('$')) return (null, null);
|
||||
var parts = value[1..].Split('|', 2);
|
||||
if (string.IsNullOrWhiteSpace(parts[0])) throw new FormatException("Platzhaltername fehlt.");
|
||||
|
||||
@@ -177,6 +177,15 @@ public sealed class QuestTemplateRenderer : ITemplateRenderer, ITemplatePreviewR
|
||||
{
|
||||
var aligned = attributes.GetValueOrDefault("align", "left").ToLowerInvariant() switch
|
||||
{ "center" => container.AlignCenter(), "right" => container.AlignRight(), _ => container.AlignLeft() };
|
||||
if (SystemVariables.Contains(content))
|
||||
{
|
||||
aligned.Text(text =>
|
||||
{
|
||||
text.DefaultTextStyle(style => ApplyTextStyle(style, attributes));
|
||||
AppendSystemVariables(text, content);
|
||||
});
|
||||
return;
|
||||
}
|
||||
var descriptor = aligned.Text(content);
|
||||
descriptor.FontSize(ParseFloat(attributes, "size", 11));
|
||||
if (ParseBool(attributes, "bold")) descriptor.SemiBold();
|
||||
@@ -216,15 +225,42 @@ public sealed class QuestTemplateRenderer : ITemplateRenderer, ITemplatePreviewR
|
||||
{
|
||||
var content = run.Placeholder is null ? run.Text
|
||||
: values.TryGetValue(run.Placeholder, out var value) ? Format(value, run.Format) : "";
|
||||
var span = text.Span(content).FontSize(fontSize);
|
||||
if (globalBold || run.Bold) span.SemiBold();
|
||||
if (globalItalic || run.Italic) span.Italic();
|
||||
if (globalUnderline || run.Underline) span.Underline();
|
||||
if (color is not null) span.FontColor(color);
|
||||
foreach (var span in AppendSystemVariables(text, content))
|
||||
{
|
||||
span.FontSize(fontSize);
|
||||
if (globalBold || run.Bold) span.SemiBold();
|
||||
if (globalItalic || run.Italic) span.Italic();
|
||||
if (globalUnderline || run.Underline) span.Underline();
|
||||
if (color is not null) span.FontColor(color);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static TextStyle ApplyTextStyle(TextStyle style, IReadOnlyDictionary<string, string> attributes)
|
||||
{
|
||||
style = style.FontSize(ParseFloat(attributes, "size", 11));
|
||||
if (ParseBool(attributes, "bold")) style = style.SemiBold();
|
||||
if (ParseBool(attributes, "italic")) style = style.Italic();
|
||||
if (ParseBool(attributes, "underline")) style = style.Underline();
|
||||
if (attributes.TryGetValue("color", out var color)) style = style.FontColor(color);
|
||||
return style;
|
||||
}
|
||||
|
||||
private static IEnumerable<TextSpanDescriptor> AppendSystemVariables(TextDescriptor text, string content)
|
||||
{
|
||||
foreach (var part in SystemVariables.Split(content))
|
||||
{
|
||||
yield return part.Name switch
|
||||
{
|
||||
SystemVariables.CurrentPage => text.CurrentPageNumber(),
|
||||
SystemVariables.MaximumPageNumber => text.TotalPages(),
|
||||
SystemVariables.Today => text.Span(DateTime.Today.ToString("d", CultureInfo.GetCultureInfo("de-DE"))),
|
||||
_ => text.Span(part.Text),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, string> TextAttributes(TemplateManifest manifest, string? placeholder,
|
||||
IReadOnlyDictionary<string, string> elementAttributes)
|
||||
{
|
||||
|
||||
@@ -119,6 +119,26 @@ haben. Haupt- und Folgeseitenlayout müssen dieselbe Seitengröße besitzen; ihr
|
||||
technischen Gründen dieselbe Position und Größe haben. Ohne Folgeseitenlayout werden die statischen
|
||||
Elemente der ersten Seite auf jeder erzeugten Seite wiederholt.
|
||||
|
||||
## Systemvariablen
|
||||
|
||||
Textinhalte können drei vom Renderer bereitgestellte Variablen verwenden. Sie werden nicht im
|
||||
Manifest deklariert und nicht vom `ITemplateDataProvider` geliefert:
|
||||
|
||||
- `$$today` – aktuelles lokales Datum im deutschen Kurzformat
|
||||
- `$$curPage` – aktuelle Seitenzahl
|
||||
- `$$maxPageNum` – Gesamtzahl der Seiten
|
||||
|
||||
Sie können allein oder innerhalb eines Literals stehen, beispielsweise:
|
||||
|
||||
```text
|
||||
TEXT 20 10 "Stand: $$today" size=9
|
||||
TEXT 145 285 "Seite $$curPage von $$maxPageNum" size=9 align=right
|
||||
```
|
||||
|
||||
QuestPDF löst aktuelle und gesamte Seitenzahl während der Dokumenterzeugung auf. Dafür ist kein
|
||||
zusätzlicher Renderdurchlauf durch die Anwendung erforderlich. Die Variablen funktionieren auch in
|
||||
konstanten Rich-Text-Platzhaltern.
|
||||
|
||||
## Konstante Platzhalter und Hervorhebung
|
||||
|
||||
Ein Platzhalter kann seinen Wert vollständig im Vorlagenpaket tragen. `IsConstant=true` bewirkt,
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
namespace LehrerApp.Templating;
|
||||
|
||||
internal static class SystemVariables
|
||||
{
|
||||
internal const string CurrentPage = "curPage";
|
||||
internal const string MaximumPageNumber = "maxPageNum";
|
||||
internal const string Today = "today";
|
||||
|
||||
private static readonly string[] Names = [CurrentPage, MaximumPageNumber, Today];
|
||||
|
||||
internal static bool IsStandalone(string value) =>
|
||||
TryRead(value, 0, out var length) && length == value.Length;
|
||||
|
||||
internal static bool Contains(string value) => Names.Any(name =>
|
||||
value.Contains("$$" + name, StringComparison.Ordinal));
|
||||
|
||||
internal static bool TryRead(string source, int index, out int length)
|
||||
{
|
||||
length = 0;
|
||||
if (index < 0 || index + 2 > source.Length || source[index] != '$' || source[index + 1] != '$')
|
||||
return false;
|
||||
foreach (var name in Names)
|
||||
{
|
||||
var token = "$$" + name;
|
||||
if (!source.AsSpan(index).StartsWith(token, StringComparison.Ordinal)) continue;
|
||||
var end = index + token.Length;
|
||||
if (end < source.Length && (char.IsLetterOrDigit(source[end]) || source[end] is '_' or '-' or '.'))
|
||||
continue;
|
||||
length = token.Length;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static IEnumerable<(string Text, string? Name)> Split(string source)
|
||||
{
|
||||
var literalStart = 0;
|
||||
for (var index = 0; index < source.Length;)
|
||||
{
|
||||
if (!TryRead(source, index, out var length)) { index++; continue; }
|
||||
if (index > literalStart) yield return (source[literalStart..index], null);
|
||||
yield return (source.Substring(index, length), source.Substring(index + 2, length - 2));
|
||||
index += length;
|
||||
literalStart = index;
|
||||
}
|
||||
if (literalStart < source.Length) yield return (source[literalStart..], null);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,13 @@ public static class TemplateRichText
|
||||
&& source[index + 1] is '\\' or '[' or '$')
|
||||
{ literal.Append(source[index + 1]); index += 2; continue; }
|
||||
|
||||
if (source[index] == '$' && SystemVariables.TryRead(source, index, out var systemLength))
|
||||
{
|
||||
literal.Append(source, index, systemLength);
|
||||
index += systemLength;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryTag(source, index, out var tag, out var closing, out var tagLength))
|
||||
{
|
||||
Flush();
|
||||
|
||||
Reference in New Issue
Block a user