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