@@ -17,10 +17,19 @@ public sealed class TwemojiInline : LeafInline
1717 public required string Shortcode { get ; init ; }
1818}
1919
20+ /// <summary>Inline node for an icon shortcode (<c>:material-*:</c> etc.) resolved to inline SVG.</summary>
21+ public sealed class IconInline : LeafInline
22+ {
23+ public required string Name { get ; init ; }
24+ public required string Svg { get ; init ; }
25+ }
26+
2027/// <summary>
2128/// Parses <c>:shortcode:</c> emoji using Markdig's built-in shortcode table and emits a
2229/// <see cref="TwemojiInline"/> so it can be rendered as a Twemoji SVG image (mirrors
23- /// mkdocs-material's <c>pymdownx.emoji</c> + twemoji generator).
30+ /// mkdocs-material's <c>pymdownx.emoji</c> + twemoji generator). Shortcodes that aren't
31+ /// emoji but resolve to a bundled icon (<c>:material-*:</c>, <c>:octicons-*:</c>,
32+ /// <c>:fontawesome-*:</c>) emit an <see cref="IconInline"/> with inline SVG.
2433/// </summary>
2534public sealed class TwemojiInlineParser : InlineParser
2635{
@@ -48,19 +57,63 @@ public override bool Match(InlineProcessor processor, ref StringSlice slice)
4857 if ( p > end || text [ p ] != ':' ) return false ;
4958
5059 var shortcode = text . Substring ( start , p - start + 1 ) ;
51- if ( ! Map . TryGetValue ( shortcode , out var unicode ) ) return false ;
52-
5360 var startPosition = processor . GetSourcePosition ( start , out var line , out var column ) ;
54- processor . Inline = new TwemojiInline
61+
62+ if ( Map . TryGetValue ( shortcode , out var unicode ) )
5563 {
56- Unicode = unicode ,
57- Shortcode = shortcode ,
58- Span = new ( startPosition , processor . GetSourcePosition ( p ) ) ,
59- Line = line ,
60- Column = column ,
61- } ;
62- slice . Start = p + 1 ;
63- return true ;
64+ processor . Inline = new TwemojiInline
65+ {
66+ Unicode = unicode ,
67+ Shortcode = shortcode ,
68+ Span = new ( startPosition , processor . GetSourcePosition ( p ) ) ,
69+ Line = line ,
70+ Column = column ,
71+ } ;
72+ slice . Start = p + 1 ;
73+ return true ;
74+ }
75+
76+ // Not a Unicode emoji — try the bundled icon set (material/octicons/fontawesome).
77+ var name = shortcode . Trim ( ':' ) ;
78+ if ( IconRegistry . IsIconName ( name ) && IconRegistry . RenderSvg ( name ) is { } svg )
79+ {
80+ processor . Inline = new IconInline
81+ {
82+ Name = name ,
83+ Svg = svg ,
84+ Span = new ( startPosition , processor . GetSourcePosition ( p ) ) ,
85+ Line = line ,
86+ Column = column ,
87+ } ;
88+ slice . Start = p + 1 ;
89+ return true ;
90+ }
91+
92+ return false ;
93+ }
94+ }
95+
96+ /// <summary>Renders an <see cref="IconInline"/> as an inline SVG wrapped in a
97+ /// <c><span class="twemoji"></c>, carrying any attr_list classes/attributes
98+ /// (e.g. <c>{ .lg .middle }</c>) so mkdocs-material icon usage renders identically.</summary>
99+ public sealed class IconRenderer : HtmlObjectRenderer < IconInline >
100+ {
101+ protected override void Write ( HtmlRenderer renderer , IconInline obj )
102+ {
103+ if ( ! renderer . EnableHtmlForInline ) return ;
104+
105+ var attrs = obj . TryGetAttributes ( ) ;
106+ renderer . Write ( "<span class=\" twemoji" ) ;
107+ if ( attrs ? . Classes is { Count : > 0 } classes )
108+ foreach ( var c in classes )
109+ renderer . Write ( ' ' ) . Write ( c ) ;
110+ renderer . Write ( '"' ) ;
111+ if ( ! string . IsNullOrEmpty ( attrs ? . Id ) )
112+ renderer . Write ( " id=\" " ) . Write ( attrs ! . Id ) . Write ( '"' ) ;
113+ if ( attrs ? . Properties is { Count : > 0 } props )
114+ foreach ( var prop in props )
115+ renderer . Write ( ' ' ) . Write ( prop . Key ) . Write ( "=\" " ) . Write ( prop . Value ?? "" ) . Write ( '"' ) ;
116+ renderer . Write ( '>' ) . Write ( obj . Svg ) . Write ( "</span>" ) ;
64117 }
65118}
66119
@@ -119,7 +172,12 @@ public void Setup(MarkdownPipelineBuilder pipeline)
119172
120173 public void Setup ( MarkdownPipeline pipeline , IMarkdownRenderer renderer )
121174 {
122- if ( renderer is HtmlRenderer html && ! html . ObjectRenderers . Contains < TwemojiRenderer > ( ) )
123- html . ObjectRenderers . Insert ( 0 , new TwemojiRenderer ( _baseUrl ) ) ;
175+ if ( renderer is HtmlRenderer html )
176+ {
177+ if ( ! html . ObjectRenderers . Contains < TwemojiRenderer > ( ) )
178+ html . ObjectRenderers . Insert ( 0 , new TwemojiRenderer ( _baseUrl ) ) ;
179+ if ( ! html . ObjectRenderers . Contains < IconRenderer > ( ) )
180+ html . ObjectRenderers . Insert ( 0 , new IconRenderer ( ) ) ;
181+ }
124182 }
125183}
0 commit comments