Skip to content

Commit c9136de

Browse files
yotsudaclaude
andcommitted
Support inline markdown bold/italic in pptx text rendering
Parse **bold**, *italic*, __bold__, _italic_ and nested combinations like **_both_** into separate D.Run elements with appropriate RunProperties. Applies to both bullet items and plain text shapes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b261dba commit c9136de

1 file changed

Lines changed: 53 additions & 19 deletions

File tree

SlideKit/Rendering/PptxRenderer.cs

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Text.RegularExpressions;
12
using DocumentFormat.OpenXml;
23
using DocumentFormat.OpenXml.Packaging;
34
using SlideKit.Models;
@@ -144,37 +145,30 @@ private static void AddTextBox(P.ShapeTree tree, ref uint id, Shape shape, strin
144145
string bulletChar = shape.BulletChar ?? "\u2022";
145146
foreach (var item in shape.Bullets)
146147
{
147-
var runProps = new D.RunProperties { Language = "ja-JP", FontSize = fontSize, Bold = shape.Bold };
148-
runProps.Append(new D.SolidFill(new D.RgbColorModelHex { Val = color }));
149-
runProps.Append(new D.LatinFont { Typeface = font });
150-
runProps.Append(new D.EastAsianFont { Typeface = font });
151-
152-
body.Append(new D.Paragraph(
148+
var para = new D.Paragraph(
153149
new D.ParagraphProperties(
154150
new D.SpaceAfter(new D.SpacingPoints { Val = 600 }),
155151
new D.BulletFont { Typeface = "Arial" },
156152
new D.CharacterBullet { Char = bulletChar }
157-
) { LeftMargin = 342900, Indent = -342900, Alignment = alignment },
158-
new D.Run(runProps, new D.Text { Text = item })));
153+
) { LeftMargin = 342900, Indent = -342900, Alignment = alignment });
154+
155+
foreach (var run in CreateRuns(item, fontSize, color, font, shape.Bold))
156+
para.Append(run);
157+
158+
body.Append(para);
159159
}
160160
}
161161
else
162162
{
163-
var runProps = new D.RunProperties(new D.SolidFill(new D.RgbColorModelHex { Val = color }))
164-
{
165-
Language = "ja-JP",
166-
FontSize = fontSize,
167-
Bold = shape.Bold,
168-
};
169-
runProps.Append(new D.LatinFont { Typeface = font });
170-
runProps.Append(new D.EastAsianFont { Typeface = font });
163+
var para = new D.Paragraph(
164+
new D.ParagraphProperties { Alignment = alignment });
165+
foreach (var run in CreateRuns(shape.Text ?? "", fontSize, color, font, shape.Bold))
166+
para.Append(run);
171167

172168
body = new D.TextBody(
173169
new D.BodyProperties { Wrap = D.TextWrappingValues.Square, Anchor = D.TextAnchoringTypeValues.Top },
174170
new D.ListStyle(),
175-
new D.Paragraph(
176-
new D.ParagraphProperties { Alignment = alignment },
177-
new D.Run(runProps, new D.Text { Text = shape.Text ?? "" })));
171+
para);
178172
}
179173

180174
var sp = new P.Shape(
@@ -457,6 +451,46 @@ private static void AddSpeakerNotes(SlidePart slidePart, string notes)
457451
private static string Hex(string? color, string fallback = "FFFFFF")
458452
=> (color ?? fallback).TrimStart('#');
459453

454+
// Inline markdown: **bold**, *italic*, __bold__, _italic_ (and nesting like **_both_**)
455+
private static readonly Regex InlinePattern = new(
456+
@"(\*{1,3}|_{1,3})(.+?)\1",
457+
RegexOptions.Compiled);
458+
459+
/// <summary>
460+
/// Creates D.Run elements from text with inline markdown, supporting nesting.
461+
/// </summary>
462+
private static IEnumerable<D.Run> CreateRuns(string text, int fontSize, string color, string font,
463+
bool shapeBold, bool shapeItalic = false)
464+
{
465+
int pos = 0;
466+
foreach (Match m in InlinePattern.Matches(text))
467+
{
468+
if (m.Index > pos)
469+
yield return MakeRun(text[pos..m.Index], fontSize, color, font, shapeBold, shapeItalic);
470+
471+
bool isBold = m.Groups[1].Value.Length >= 2;
472+
bool isItalic = m.Groups[1].Value.Length == 1 || m.Groups[1].Value.Length == 3;
473+
474+
// Recurse into inner text to handle nesting (e.g. **_both_**)
475+
foreach (var run in CreateRuns(m.Groups[2].Value, fontSize, color, font,
476+
shapeBold || isBold, shapeItalic || isItalic))
477+
yield return run;
478+
479+
pos = m.Index + m.Length;
480+
}
481+
if (pos < text.Length)
482+
yield return MakeRun(text[pos..], fontSize, color, font, shapeBold, shapeItalic);
483+
}
484+
485+
private static D.Run MakeRun(string text, int fontSize, string color, string font, bool bold, bool italic)
486+
{
487+
var props = new D.RunProperties { Language = "ja-JP", FontSize = fontSize, Bold = bold, Italic = italic };
488+
props.Append(new D.SolidFill(new D.RgbColorModelHex { Val = color }));
489+
props.Append(new D.LatinFont { Typeface = font });
490+
props.Append(new D.EastAsianFont { Typeface = font });
491+
return new D.Run(props, new D.Text { Text = text });
492+
}
493+
460494
// ---- Presentation initialization (fallback when no template) ----
461495

462496
private static void InitializePresentation(PresentationDocument pres, SlideTheme theme)

0 commit comments

Comments
 (0)