mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Add custom RtfRenderer
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Velopack.Packaging.Rtf.Inlines;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for an <see cref="AutolinkInline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{AutolinkInline}" />
|
||||
public class RtfAutolinkInlineRenderer : RtfObjectRenderer<AutolinkInline>
|
||||
{
|
||||
public string? Rel { get; set; }
|
||||
|
||||
protected override void Write(RtfRenderer renderer, AutolinkInline obj)
|
||||
{
|
||||
// Render as underlined, blue text (simulating a link in RTF)
|
||||
renderer.Write("{\\ul\\cf1 ");
|
||||
renderer.WriteEscape(obj.Url);
|
||||
renderer.Write("}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Velopack.Packaging.Rtf.Inlines;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for a <see cref="CodeInline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{CodeInline}" />
|
||||
public class RtfCodeInlineRenderer : RtfObjectRenderer<CodeInline>
|
||||
{
|
||||
protected override void Write(RtfRenderer renderer, CodeInline obj)
|
||||
{
|
||||
renderer.Write("{\\fmodern ");
|
||||
renderer.WriteEscape(obj.Content);
|
||||
renderer.Write("}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Velopack.Packaging.Rtf.Inlines;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for a <see cref="DelimiterInline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{DelimiterInline}" />
|
||||
public class RtfDelimiterInlineRenderer : RtfObjectRenderer<DelimiterInline>
|
||||
{
|
||||
protected override void Write(RtfRenderer renderer, DelimiterInline obj)
|
||||
{
|
||||
renderer.WriteEscape(obj.ToLiteral());
|
||||
renderer.WriteChildren(obj);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Velopack.Packaging.Rtf.Inlines;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for an <see cref="EmphasisInline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{EmphasisInline}" />
|
||||
public class RtfEmphasisInlineRenderer : RtfObjectRenderer<EmphasisInline>
|
||||
{
|
||||
public delegate string? GetTagDelegate(EmphasisInline obj);
|
||||
public GetTagDelegate? GetTag { get; set; }
|
||||
|
||||
protected override void Write(RtfRenderer renderer, EmphasisInline obj)
|
||||
{
|
||||
// Use bold or italic for emphasis
|
||||
var tag = GetTag?.Invoke(obj) ?? GetDefaultTag(obj);
|
||||
if (tag == "b")
|
||||
renderer.Write("{\\b ");
|
||||
else if (tag == "i")
|
||||
renderer.Write("{\\i ");
|
||||
renderer.WriteChildren(obj);
|
||||
if (tag == "b" || tag == "i")
|
||||
renderer.Write("}");
|
||||
}
|
||||
public static string? GetDefaultTag(EmphasisInline obj)
|
||||
{
|
||||
return obj.DelimiterChar == '*' || obj.DelimiterChar == '_' ? (obj.DelimiterCount == 2 ? "b" : "i") : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Velopack.Packaging.Rtf.Inlines;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for a <see cref="LineBreakInline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{LineBreakInline}" />
|
||||
public class RtfLineBreakInlineRenderer : RtfObjectRenderer<LineBreakInline>
|
||||
{
|
||||
public bool RenderAsHardlineBreak { get; set; }
|
||||
|
||||
protected override void Write(RtfRenderer renderer, LineBreakInline obj)
|
||||
{
|
||||
if (renderer.IsLastInContainer) return;
|
||||
renderer.Write("\\line "); // RTF line break
|
||||
renderer.EnsureLine();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Velopack.Packaging.Rtf.Inlines;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for a <see cref="LinkInline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{LinkInline}" />
|
||||
public class RtfLinkInlineRenderer : RtfObjectRenderer<LinkInline>
|
||||
{
|
||||
public string? Rel { get; set; }
|
||||
|
||||
protected override void Write(RtfRenderer renderer, LinkInline link)
|
||||
{
|
||||
if (link.IsImage)
|
||||
{
|
||||
// Skip images entirely in RTF output
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Simulate link: underline, blue
|
||||
renderer.Write("{\\ul\\cf1 ");
|
||||
renderer.WriteChildren(link);
|
||||
renderer.Write("}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Velopack.Packaging.Rtf.Inlines;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for a <see cref="LiteralInline"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{LiteralInline}" />
|
||||
public class RtfLiteralInlineRenderer : RtfObjectRenderer<LiteralInline>
|
||||
{
|
||||
protected override void Write(RtfRenderer renderer, LiteralInline obj)
|
||||
{
|
||||
renderer.WriteEscape(ref obj.Content);
|
||||
}
|
||||
}
|
||||
20
src/vpk/Velopack.Packaging/Rtf/RtfBlockRenderer.cs
Normal file
20
src/vpk/Velopack.Packaging/Rtf/RtfBlockRenderer.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Velopack.Packaging.Rtf;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for a <see cref="HtmlBlock"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{HtmlBlock}" />
|
||||
public class RtfBlockRenderer : RtfObjectRenderer<HtmlBlock>
|
||||
{
|
||||
protected override void Write(RtfRenderer renderer, HtmlBlock obj)
|
||||
{
|
||||
// Placeholder: Write the block as RTF raw text (should be adapted for real RTF output)
|
||||
renderer.Write("{\\rtf1 ");
|
||||
renderer.WriteLeafRawLines(obj, true, false);
|
||||
renderer.Write("}\n");
|
||||
}
|
||||
}
|
||||
32
src/vpk/Velopack.Packaging/Rtf/RtfCodeBlockRenderer.cs
Normal file
32
src/vpk/Velopack.Packaging/Rtf/RtfCodeBlockRenderer.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Velopack.Packaging.Rtf;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for a <see cref="CodeBlock"/> and <see cref="FencedCodeBlock"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{CodeBlock}" />
|
||||
public class RtfCodeBlockRenderer : RtfObjectRenderer<CodeBlock>
|
||||
{
|
||||
public bool OutputAttributesOnPre { get; set; }
|
||||
|
||||
protected override void Write(RtfRenderer renderer, CodeBlock obj)
|
||||
{
|
||||
renderer.EnsureLine();
|
||||
if (obj is FencedCodeBlock { Info: string info })
|
||||
{
|
||||
// For RTF, just render code in a monospaced block with a border
|
||||
renderer.Write("{\\pard\\fmodern\\brdrb\\brdrs ");
|
||||
renderer.WriteLeafRawLines(obj, true, true);
|
||||
renderer.Write(" \\par}\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Write("{\\pard\\fmodern ");
|
||||
renderer.WriteLeafRawLines(obj, true, true);
|
||||
renderer.Write(" \\par}\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/vpk/Velopack.Packaging/Rtf/RtfHeadingRenderer.cs
Normal file
24
src/vpk/Velopack.Packaging/Rtf/RtfHeadingRenderer.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Velopack.Packaging.Rtf;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for a <see cref="HeadingBlock"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{HeadingBlock}" />
|
||||
public class RtfHeadingRenderer : RtfObjectRenderer<HeadingBlock>
|
||||
{
|
||||
protected override void Write(RtfRenderer renderer, HeadingBlock obj)
|
||||
{
|
||||
// Map heading levels to RTF font sizes (example: h1 = 32pt, h2 = 28pt, ...)
|
||||
int[] fontSizes = [36, 30, 24, 20, 16, 14];
|
||||
int level = obj.Level - 1;
|
||||
int fontSize = (level >= 0 && level < fontSizes.Length) ? fontSizes[level] : 12;
|
||||
|
||||
renderer.Write($"\\line{{\\pard\\b\\fs{fontSize} "); // RTF font size is half-points
|
||||
renderer.WriteLeafInline(obj);
|
||||
renderer.Write(" \\par}\n");
|
||||
}
|
||||
}
|
||||
37
src/vpk/Velopack.Packaging/Rtf/RtfListRenderer.cs
Normal file
37
src/vpk/Velopack.Packaging/Rtf/RtfListRenderer.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Velopack.Packaging.Rtf;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for a <see cref="ListBlock"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{ListBlock}" />
|
||||
public class RtfListRenderer : RtfObjectRenderer<ListBlock>
|
||||
{
|
||||
protected override void Write(RtfRenderer renderer, ListBlock listBlock)
|
||||
{
|
||||
renderer.EnsureLine();
|
||||
if (listBlock.IsOrdered)
|
||||
{
|
||||
renderer.Write("{\\pard ");
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Write("{\\pard ");
|
||||
}
|
||||
foreach (var item in listBlock)
|
||||
{
|
||||
var listItem = (ListItemBlock)item;
|
||||
var previousImplicit = renderer.ImplicitParagraph;
|
||||
renderer.ImplicitParagraph = !listBlock.IsLoose;
|
||||
renderer.EnsureLine();
|
||||
renderer.Write("\\bullet "); // RTF bullet character
|
||||
renderer.WriteChildren(listItem);
|
||||
renderer.Write(" \\par");
|
||||
renderer.ImplicitParagraph = previousImplicit;
|
||||
}
|
||||
renderer.Write("}\n");
|
||||
}
|
||||
}
|
||||
16
src/vpk/Velopack.Packaging/Rtf/RtfObjectRenderer.cs
Normal file
16
src/vpk/Velopack.Packaging/Rtf/RtfObjectRenderer.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Syntax;
|
||||
using Markdig.Syntax.Inlines;
|
||||
|
||||
namespace Velopack.Packaging.Rtf;
|
||||
|
||||
/// <summary>
|
||||
/// A base class for RTF rendering <see cref="Block"/> and <see cref="Inline"/> Markdown objects.
|
||||
/// </summary>
|
||||
/// <typeparam name="TObject">The type of the object.</typeparam>
|
||||
/// <seealso cref="IMarkdownObjectRenderer" />
|
||||
public abstract class RtfObjectRenderer<TObject> : MarkdownObjectRenderer<RtfRenderer, TObject> where TObject : MarkdownObject
|
||||
{
|
||||
}
|
||||
42
src/vpk/Velopack.Packaging/Rtf/RtfParagraphRenderer.cs
Normal file
42
src/vpk/Velopack.Packaging/Rtf/RtfParagraphRenderer.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Velopack.Packaging.Rtf;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for a <see cref="ParagraphBlock"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{ParagraphBlock}" />
|
||||
public class RtfParagraphRenderer : RtfObjectRenderer<ParagraphBlock>
|
||||
{
|
||||
protected override void Write(RtfRenderer renderer, ParagraphBlock obj)
|
||||
{
|
||||
if (!renderer.ImplicitParagraph)
|
||||
{
|
||||
renderer.Write("{\\pard "); // RTF paragraph start
|
||||
}
|
||||
renderer.WriteLeafInline(obj);
|
||||
if (!renderer.ImplicitParagraph)
|
||||
{
|
||||
renderer.Write(" \\par}"); // RTF paragraph end
|
||||
|
||||
// Only write \line if next block is not a heading
|
||||
bool nextIsHeading = false;
|
||||
if (obj.Parent is ContainerBlock parent)
|
||||
{
|
||||
int index = parent.IndexOf(obj);
|
||||
if (index >= 0 && index + 1 < parent.Count)
|
||||
{
|
||||
var next = parent[index + 1];
|
||||
nextIsHeading = next is Markdig.Syntax.HeadingBlock;
|
||||
}
|
||||
}
|
||||
if (!nextIsHeading)
|
||||
{
|
||||
renderer.Write("\\line ");
|
||||
}
|
||||
renderer.EnsureLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/vpk/Velopack.Packaging/Rtf/RtfQuoteBlockRenderer.cs
Normal file
24
src/vpk/Velopack.Packaging/Rtf/RtfQuoteBlockRenderer.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Velopack.Packaging.Rtf;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for a <see cref="QuoteBlock"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{QuoteBlock}" />
|
||||
public class RtfQuoteBlockRenderer : RtfObjectRenderer<QuoteBlock>
|
||||
{
|
||||
protected override void Write(RtfRenderer renderer, QuoteBlock obj)
|
||||
{
|
||||
renderer.EnsureLine();
|
||||
renderer.Write("{\\pard\\li720 "); // Indent for quote
|
||||
var savedImplicitParagraph = renderer.ImplicitParagraph;
|
||||
renderer.ImplicitParagraph = false;
|
||||
renderer.WriteChildren(obj);
|
||||
renderer.ImplicitParagraph = savedImplicitParagraph;
|
||||
renderer.Write(" \\par}\n");
|
||||
renderer.EnsureLine();
|
||||
}
|
||||
}
|
||||
157
src/vpk/Velopack.Packaging/Rtf/RtfRenderer.cs
Normal file
157
src/vpk/Velopack.Packaging/Rtf/RtfRenderer.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Helpers;
|
||||
using Markdig.Renderers;
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Velopack.Packaging.Rtf;
|
||||
|
||||
/// <summary>
|
||||
/// Default RTF renderer for a Markdown <see cref="MarkdownDocument"/> object.
|
||||
/// </summary>
|
||||
/// <seealso cref="TextRendererBase{RtfRenderer}" />
|
||||
public class RtfRenderer : TextRendererBase<RtfRenderer>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RtfRenderer"/> class.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer.</param>
|
||||
public RtfRenderer(TextWriter writer) : base(writer)
|
||||
{
|
||||
// Default block renderers
|
||||
ObjectRenderers.Add(new RtfCodeBlockRenderer());
|
||||
ObjectRenderers.Add(new RtfListRenderer());
|
||||
ObjectRenderers.Add(new RtfHeadingRenderer());
|
||||
ObjectRenderers.Add(new RtfBlockRenderer());
|
||||
ObjectRenderers.Add(new RtfParagraphRenderer());
|
||||
ObjectRenderers.Add(new RtfQuoteBlockRenderer());
|
||||
ObjectRenderers.Add(new RtfThematicBreakRenderer());
|
||||
|
||||
// Inline renderers
|
||||
ObjectRenderers.Add(new Inlines.RtfAutolinkInlineRenderer());
|
||||
ObjectRenderers.Add(new Inlines.RtfCodeInlineRenderer());
|
||||
ObjectRenderers.Add(new Inlines.RtfDelimiterInlineRenderer());
|
||||
ObjectRenderers.Add(new Inlines.RtfEmphasisInlineRenderer());
|
||||
ObjectRenderers.Add(new Inlines.RtfLineBreakInlineRenderer());
|
||||
ObjectRenderers.Add(new Inlines.RtfLinkInlineRenderer());
|
||||
ObjectRenderers.Add(new Inlines.RtfLiteralInlineRenderer());
|
||||
}
|
||||
|
||||
public bool ImplicitParagraph { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Writes the lines of a <see cref="LeafBlock"/>
|
||||
/// </summary>
|
||||
/// <param name="leafBlock">The leaf block.</param>
|
||||
/// <param name="writeEndOfLines">if set to <c>true</c> write end of lines.</param>
|
||||
/// <param name="escape">if set to <c>true</c> escape the content for RTF</param>
|
||||
/// <param name="softEscape">Only escape minimal RTF chars</param>
|
||||
/// <returns>This instance</returns>
|
||||
public RtfRenderer WriteLeafRawLines(LeafBlock leafBlock, bool writeEndOfLines, bool escape, bool softEscape = false)
|
||||
{
|
||||
if (leafBlock is null) throw new ArgumentNullException(nameof(leafBlock));
|
||||
var slices = leafBlock.Lines.Lines;
|
||||
if (slices is not null) {
|
||||
for (int i = 0; i < slices.Length; i++) {
|
||||
ref StringSlice slice = ref slices[i].Slice;
|
||||
if (slice.Text is null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!writeEndOfLines && i > 0) {
|
||||
WriteLine();
|
||||
}
|
||||
|
||||
ReadOnlySpan<char> span = slice.AsSpan();
|
||||
if (escape) {
|
||||
WriteEscape(span, softEscape);
|
||||
} else {
|
||||
Write(span);
|
||||
}
|
||||
|
||||
if (writeEndOfLines) {
|
||||
WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the content escaped for RTF, converting Unicode to RTF Unicode escapes.
|
||||
/// </summary>
|
||||
/// <param name="content">The string content.</param>
|
||||
/// <param name="softEscape">If true, only escape minimal RTF chars.</param>
|
||||
public void WriteEscape(string? content, bool softEscape = false)
|
||||
{
|
||||
if (content == null) return;
|
||||
for (int i = 0; i < content.Length; i++) {
|
||||
char c = content[i];
|
||||
if (char.IsHighSurrogate(c) && i + 1 < content.Length && char.IsLowSurrogate(content[i + 1])) {
|
||||
int codepoint = char.ConvertToUtf32(c, content[i + 1]);
|
||||
Write($"\\u{codepoint}?"); // RTF spec: use '?' as fallback char
|
||||
i++; // skip low surrogate
|
||||
} else if (c == '\\') Write("\\\\");
|
||||
else if (c == '{') Write("\\{");
|
||||
else if (c == '}') Write("\\}");
|
||||
else if (c < 0x20 || (c >= 0x7F && c <= 0x9F)) { } // skip control characters
|
||||
else if (c <= 0x7F) // ASCII
|
||||
Write(c);
|
||||
else // BMP Unicode
|
||||
Write($"\\u{(int) c}{c}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the content escaped for RTF, converting Unicode to RTF Unicode escapes.
|
||||
/// </summary>
|
||||
/// <param name="span">The character span.</param>
|
||||
/// <param name="softEscape">If true, only escape minimal RTF chars.</param>
|
||||
public void WriteEscape(ReadOnlySpan<char> span, bool softEscape = false)
|
||||
{
|
||||
for (int i = 0; i < span.Length; i++) {
|
||||
char c = span[i];
|
||||
if (char.IsHighSurrogate(c) && i + 1 < span.Length && char.IsLowSurrogate(span[i + 1])) {
|
||||
int codepoint = char.ConvertToUtf32(c, span[i + 1]);
|
||||
Write($"\\u{codepoint}?"); // RTF spec: use '?' as fallback char
|
||||
i++; // skip low surrogate
|
||||
} else if (c == '\\') Write("\\\\");
|
||||
else if (c == '{') Write("\\{");
|
||||
else if (c == '}') Write("\\}");
|
||||
else if (c < 0x20 || (c >= 0x7F && c <= 0x9F)) { } // skip control characters
|
||||
else if (c <= 0x7F) // ASCII
|
||||
Write(c);
|
||||
else // BMP Unicode
|
||||
Write($"\\u{(int) c}{c}");
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteEscape(ref Markdig.Helpers.StringSlice slice, bool softEscape = false)
|
||||
{
|
||||
WriteEscape(slice.AsSpan(), softEscape);
|
||||
}
|
||||
|
||||
public void WriteEscape(Markdig.Helpers.StringSlice slice, bool softEscape = false)
|
||||
{
|
||||
WriteEscape(slice.AsSpan(), softEscape);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the RTF document start block.
|
||||
/// </summary>
|
||||
public void WriteRtfStart()
|
||||
{
|
||||
WriteLine(@"{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1031{\fonttbl{\f0\fnil\fcharset0 Calibri;}}");
|
||||
WriteLine(@"{\colortbl ;\red0\green0\blue0;}");
|
||||
WriteLine(@"\viewkind4\uc1\pard\sa200\sl276\slmult1\f0\fs19\lang7");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the RTF document end block.
|
||||
/// </summary>
|
||||
public void WriteRtfEnd()
|
||||
{
|
||||
WriteLine("}");
|
||||
}
|
||||
}
|
||||
17
src/vpk/Velopack.Packaging/Rtf/RtfThematicBreakRenderer.cs
Normal file
17
src/vpk/Velopack.Packaging/Rtf/RtfThematicBreakRenderer.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
#nullable enable
|
||||
|
||||
using Markdig.Syntax;
|
||||
|
||||
namespace Velopack.Packaging.Rtf;
|
||||
|
||||
/// <summary>
|
||||
/// An RTF renderer for a <see cref="ThematicBreakBlock"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="RtfObjectRenderer{ThematicBreakBlock}" />
|
||||
public class RtfThematicBreakRenderer : RtfObjectRenderer<ThematicBreakBlock>
|
||||
{
|
||||
protected override void Write(RtfRenderer renderer, ThematicBreakBlock obj)
|
||||
{
|
||||
renderer.Write("{\\pard\\qr\\sl0\\slmult1\\line}\n"); // RTF horizontal rule (simulated with a line break)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user