Add global diagram-theme directive with fence-level override precedence#130
Merged
jongalloway merged 5 commits intomainfrom Mar 14, 2026
Merged
Conversation
…edence Co-authored-by: jongalloway <68539+jongalloway@users.noreply.github.com>
…eam variable Co-authored-by: jongalloway <68539+jongalloway@users.noreply.github.com>
Copilot created this pull request from a session on behalf of
jongalloway
March 14, 2026 22:40
View session
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a deck-level diagram-theme front-matter directive so authors can set a default DiagramForge theme for all mermaid / diagram fences, while keeping fence-level theme: as the highest-precedence override.
Changes:
- Introduces
SlideDeck.DiagramThemeand parsesdiagram-themefrom front matter. - Threads the deck-level theme through the PPTX render pipeline and injects
theme: ...into diagram fence YAML front matter when missing. - Adds parser/renderer tests plus updates documentation and the diagrams sample deck.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/MarpToPptx.Tests/PptxRendererTests.cs | Adds renderer tests validating SVG output and precedence behavior for deck-level diagram theming. |
| tests/MarpToPptx.Tests/ParserTests.cs | Adds parser tests ensuring diagram-theme is parsed, normalized, and stored correctly. |
| src/MarpToPptx.Pptx/Rendering/OpenXmlPptxRenderer.cs | Carries deck-level diagram theme through SlideRenderContext and injects it into diagram source when needed. |
| src/MarpToPptx.Core/Parsing/MarpMarkdownParser.cs | Reads diagram-theme from deck front matter and stores it on SlideDeck. |
| src/MarpToPptx.Core/Models/SlideDeck.cs | Adds the DiagramTheme property to the deck model. |
| samples/09-diagrams.md | Demonstrates deck-level default theme and per-fence overrides in the sample deck. |
| doc/marp-markdown.md | Documents the new directive, syntax, and precedence. |
You can also share your feedback on Copilot code review. Take the survey.
Comment on lines
+5739
to
+5785
| // ── diagram-theme directive tests ────────────────────────────────────────── | ||
|
|
||
| [Fact] | ||
| public void Renderer_GlobalDiagramTheme_MermaidFence_WithoutFenceLevelTheme_RendersSvg() | ||
| { | ||
| // A deck with diagram-theme: prism should apply the theme to mermaid fences without | ||
| // their own fence-level theme, and the diagram must render as an SVG picture shape. | ||
| using var workspace = TestWorkspace.Create(); | ||
|
|
||
| var markdownPath = workspace.WriteMarkdown( | ||
| "deck.md", | ||
| """ | ||
| --- | ||
| diagram-theme: prism | ||
| --- | ||
|
|
||
| # Mermaid Diagram | ||
|
|
||
| ```mermaid | ||
| flowchart LR | ||
| A --> B | ||
| ``` | ||
| """); | ||
|
|
||
| var outputPath = workspace.GetPath("deck.pptx"); | ||
| RenderDeck(markdownPath, outputPath, workspace.RootPath); | ||
|
|
||
| using var document = PresentationDocument.Open(outputPath, false); | ||
| var slidePart = document.PresentationPart!.SlideParts.Single(); | ||
|
|
||
| var pictures = slidePart.Slide!.Descendants<P.Picture>().ToArray(); | ||
| Assert.NotEmpty(pictures); | ||
|
|
||
| var svgPart = slidePart.ImageParts | ||
| .FirstOrDefault(p => string.Equals(p.ContentType, "image/svg+xml", StringComparison.OrdinalIgnoreCase)); | ||
| Assert.NotNull(svgPart); | ||
|
|
||
| using var stream = svgPart!.GetStream(); | ||
| var svg = new System.IO.StreamReader(stream).ReadToEnd(); | ||
| Assert.Contains("<svg", svg, StringComparison.OrdinalIgnoreCase); | ||
|
|
||
| var textRuns = slidePart.Slide.Descendants<A.Text>().Select(t => t.Text).ToArray(); | ||
| Assert.DoesNotContain(textRuns, t => t.StartsWith("Mermaid parse error:", StringComparison.Ordinal)); | ||
|
|
||
| var validationErrors = new OpenXmlPackageValidator().Validate(document); | ||
| Assert.Empty(validationErrors); | ||
| } |
Comment on lines
+5779
to
+5782
|
|
||
| var textRuns = slidePart.Slide.Descendants<A.Text>().Select(t => t.Text).ToArray(); | ||
| Assert.DoesNotContain(textRuns, t => t.StartsWith("Mermaid parse error:", StringComparison.Ordinal)); | ||
|
|
Comment on lines
+1914
to
+1941
| if (source.StartsWith("---", StringComparison.Ordinal) && | ||
| (source.Length == 3 || source[3] is '\n' or '\r')) | ||
| { | ||
| var openEnd = source.IndexOf('\n', 3); | ||
| if (openEnd < 0) | ||
| return source; // Opening delimiter with no newline — return unchanged. | ||
|
|
||
| var bodyStart = openEnd + 1; | ||
|
|
||
| // Search for the closing --- delimiter. Start from openEnd so the \n that | ||
| // terminates the opening delimiter line is included in the search window, | ||
| // which correctly handles an empty front matter block (--- immediately after ---\n). | ||
| var closeLineIdx = source.IndexOf("\n---", openEnd, StringComparison.Ordinal); | ||
| if (closeLineIdx >= 0) | ||
| { | ||
| var frontMatterBody = closeLineIdx >= bodyStart | ||
| ? source[bodyStart..closeLineIdx] | ||
| : string.Empty; | ||
|
|
||
| // If the fence already specifies a theme, it takes precedence — return unchanged. | ||
| // Use a span-based line scan to avoid allocating a split array. | ||
| if (FrontMatterBodyHasThemeKey(frontMatterBody)) | ||
| { | ||
| return source; | ||
| } | ||
|
|
||
| // Inject the global theme as the first key inside the existing front matter block. | ||
| return source[..bodyStart] + $"theme: {globalDiagramTheme}\n" + source[bodyStart..]; |
Comment on lines
+204
to
+211
| ````markdown | ||
| ```mermaid | ||
| --- | ||
| theme: dracula | ||
| --- | ||
| flowchart LR | ||
| A --> B | ||
| ```` |
samples/09-diagrams.md
Outdated
| ## Mermaid With Dracula Theme | ||
|
|
||
| This Mermaid diagram uses DiagramForge frontmatter to switch to the built-in Dracula theme and apply additional styling overrides. | ||
| This Mermaid diagram uses DiagramForge frontmatter to override the deck-level `prism` theme with `theme: dracula`, plus additional styling options. |
Owner
|
@copilot review comments |
…oc closing fence, front matter spelling Co-authored-by: jongalloway <68539+jongalloway@users.noreply.github.com>
Contributor
Author
Addressed all five review comments in commit
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a deck-level
diagram-themefront matter directive so authors can set a preferred DiagramForge theme for allmermaidanddiagramfences without repeating it per-fence. Fence-leveltheme:always wins.Precedence
theme:in embedded YAML front matter — unchanged, highest prioritydiagram-theme— injected when fence has no themeExample
Changes
Model / Parser
SlideDeck.DiagramTheme— new property stores the parsed directive value (whitespace-only →null)MarpMarkdownParser— readsdiagram-themefrom front matter during deck parsingRenderer
SlideRenderContext.GlobalDiagramTheme— carries the deck-level value through the render pipelineInjectDiagramThemeIfNeeded— injectstheme: Xinto the diagram source's YAML front matter before handing off toDiagramRenderer.Render(). Creates a minimal---…---block if the source has none; no-ops if a fence-leveltheme:is already presentFrontMatterBodyHasThemeKey— span-based line scanner (avoids string-split allocation) used by the above helperTests
null, whitespace-only →null, stored inFrontMatterdictmermaidfence, deck-level applied todiagramfence, fence-level override produces different SVG than deck-level, no-regression without directive, front matter with other keys but notheme:receives injection while explicittheme: draculais preservedDocs / Sample
doc/marp-markdown.md— new table entry + "Diagram Theme Directive" section documenting precedence and syntaxsamples/09-diagrams.md— addsdiagram-theme: prismto front matter; labels slides showing deck-level inheritance vs. fence-level override