jekyll-mermaid-prebuild 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +33 -1
- data/lib/jekyll-mermaid-prebuild/configuration.rb +124 -1
- data/lib/jekyll-mermaid-prebuild/generator.rb +70 -21
- data/lib/jekyll-mermaid-prebuild/mmdc_wrapper.rb +11 -1
- data/lib/jekyll-mermaid-prebuild/processor.rb +16 -6
- data/lib/jekyll-mermaid-prebuild/svg_post_processor.rb +24 -2
- data/lib/jekyll-mermaid-prebuild/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f20707d63b254ecd0493fd044b7bb413476f2f5908bfd5e1f8aaeab4a7c4af88
|
|
4
|
+
data.tar.gz: c145bf64bedfb029cdb43f8c513dd302c2f1be8216c42b1c1f17d560244e8f5f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c076fb8bbb8c812e83a04cd985226e909d9c9edf0f4d49275abfb357a3f66ca2b3db3c5a1dab7486e3bbb29eb5ca0cf8ec61e6d4d617a28203a2dad8b9c7082d
|
|
7
|
+
data.tar.gz: ebe336837f347ed991aeafe75891e6cc4f47005ea8516a344a2e4c55d63144324e866c419db79addf6a2ded2f7749cf762638ec09c9d4fe1f1bef93d7b500709
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.0](https://github.com/Texarkanine/jekyll-mermaid-prebuild/compare/v0.4.0...v0.5.0) (2026-03-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* support "dark mode" (resolves [#11](https://github.com/Texarkanine/jekyll-mermaid-prebuild/issues/11)) ([#20](https://github.com/Texarkanine/jekyll-mermaid-prebuild/issues/20)) ([1ae9322](https://github.com/Texarkanine/jekyll-mermaid-prebuild/commit/1ae93225d9f6089d3d85ce18d06f44310fea3895))
|
|
9
|
+
|
|
3
10
|
## [0.4.0](https://github.com/Texarkanine/jekyll-mermaid-prebuild/compare/v0.3.2...v0.4.0) (2026-03-22)
|
|
4
11
|
|
|
5
12
|
|
data/README.md
CHANGED
|
@@ -94,6 +94,12 @@ Add to your `_config.yml`:
|
|
|
94
94
|
mermaid_prebuild:
|
|
95
95
|
enabled: true # default: true
|
|
96
96
|
output_dir: assets/svg # default: assets/svg
|
|
97
|
+
prefers-color-scheme:
|
|
98
|
+
mode: light # light (default) | dark | auto — see [Color scheme](#color-scheme-mermaid-theme)
|
|
99
|
+
# Optional: override mmdc’s root SVG background (defaults: light=white, dark=black)
|
|
100
|
+
# background-color:
|
|
101
|
+
# light: white
|
|
102
|
+
# dark: black
|
|
97
103
|
postprocessing:
|
|
98
104
|
text_centering: true # default: true
|
|
99
105
|
overflow_protection: true # default: true
|
|
@@ -108,6 +114,7 @@ mermaid_prebuild:
|
|
|
108
114
|
|--------|---------|-------------|
|
|
109
115
|
| `enabled` | `true` | Enable/disable the plugin |
|
|
110
116
|
| `output_dir` | `assets/svg` | Directory for generated SVG files |
|
|
117
|
+
| `prefers-color-scheme` | see below | **Hash** with `mode` (`light` / `dark` / `auto`) and optional `background-color` for chart backgrounds. See [Color scheme](#color-scheme-mermaid-theme). |
|
|
111
118
|
|
|
112
119
|
#### `postprocessing` group
|
|
113
120
|
|
|
@@ -120,6 +127,31 @@ All cross-browser rendering fixes live under the `postprocessing:` key. Each can
|
|
|
120
127
|
| `edge_label_padding` | `0` | Extra SVG user units added to edge-label `<foreignObject>` widths after mmdc (off when `0`, `false`, or omitted). Applies to all diagram types. See [Cross-browser text rendering fixes](#cross-browser-text-rendering-fixes). |
|
|
121
128
|
| `emoji_width_compensation` | `{}` | Map of diagram types to booleans; see [Emoji width compensation](#emoji-width-compensation) below. |
|
|
122
129
|
|
|
130
|
+
### Color scheme (Mermaid theme)
|
|
131
|
+
|
|
132
|
+
Under `mermaid_prebuild`, the **`prefers-color-scheme`** key (hyphenated, like the CSS media feature) must be a **YAML mapping**. Use `mode` for the Mermaid theme / HTML behavior. Optionally nest **`background-color`** (same spelling as in CSS) with `light` / `dark` slots for each chart variant’s root SVG fill (mmdc always emits `background-color: white` on the root `<svg>`; the plugin rewrites that per file).
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
mermaid_prebuild:
|
|
136
|
+
prefers-color-scheme:
|
|
137
|
+
mode: auto
|
|
138
|
+
background-color:
|
|
139
|
+
light: white # default if omitted
|
|
140
|
+
dark: black # default if omitted
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
| `mode` | Behavior |
|
|
144
|
+
|--------|----------|
|
|
145
|
+
| `light` | One SVG per diagram using Mermaid’s default (light) theme. |
|
|
146
|
+
| `dark` | One SVG per diagram using mmdc’s dark theme (`mmdc -t dark`). |
|
|
147
|
+
| `auto` | Two SVGs per diagram: `{digest}.svg` (light) and `{digest}-dark.svg` (dark). The embedded HTML uses two links with a small inline stylesheet so only the variant matching the visitor’s `prefers-color-scheme` is shown. **Build cost:** each diagram runs `mmdc` twice until both files are cached. |
|
|
148
|
+
|
|
149
|
+
**Chart backgrounds:** Values are injected verbatim into the root `<svg style="...">` as the CSS token after `background-color:` (for example `white`, `black`, `#fff0aa`, `rgb(0,0,0)`, `transparent`). They are validated conservatively: values containing quotes, angle brackets, backticks, semicolons, backslashes, or control characters are rejected and the default for that slot is used, with a warning. Keep values short (max 256 characters). **Do not** put raw double quotes or HTML in these strings.
|
|
150
|
+
|
|
151
|
+
**Transparency:** The standard CSS keyword **`transparent`** is supported and is the usual choice when you want that variant’s chart to show the page behind it (for example `dark: transparent` on a dark-themed site). Fully transparent RGBA such as `rgba(0, 0, 0, 0)` is also valid if you prefer explicit alpha.
|
|
152
|
+
|
|
153
|
+
If `prefers-color-scheme` is not a hash (for example a bare string), the plugin uses `mode: light` and default backgrounds and logs a warning. Unknown `mode` values fall back to `light` with a warning. Omitting `mode` is treated as `light`.
|
|
154
|
+
|
|
123
155
|
### Cross-browser text rendering fixes
|
|
124
156
|
|
|
125
157
|
When mmdc renders a diagram, headless Chromium measures text with `getBoundingClientRect()` and sets each `<foreignObject>` to exactly that width. If the viewing browser (different OS, different fonts) renders the same text at a different width, labels can clip or shift. The plugin can apply some fixes to every generated SVG (all configurable under `postprocessing:`):
|
|
@@ -179,7 +211,7 @@ Generated SVGs are cached in `.jekyll-cache/jekyll-mermaid-prebuild/`. The cache
|
|
|
179
211
|
- Modified diagrams are automatically regenerated
|
|
180
212
|
- Different diagrams with different content get different cache keys
|
|
181
213
|
- Enabling or disabling emoji width compensation for a diagram type invalidates cache for that content (keys include compensated source when applicable)
|
|
182
|
-
- Changing `edge_label_padding`, `text_centering`,
|
|
214
|
+
- Changing `edge_label_padding`, `text_centering`, `overflow_protection`, `prefers-color-scheme` mode, or chart background colors invalidates cache keys
|
|
183
215
|
|
|
184
216
|
To clear the cache:
|
|
185
217
|
|
|
@@ -5,8 +5,19 @@ module JekyllMermaidPrebuild
|
|
|
5
5
|
class Configuration
|
|
6
6
|
DEFAULT_OUTPUT_DIR = "assets/svg"
|
|
7
7
|
CACHE_DIR = ".jekyll-cache/jekyll-mermaid-prebuild"
|
|
8
|
+
DEFAULT_CHART_BG_LIGHT = "white"
|
|
9
|
+
DEFAULT_CHART_BG_DARK = "black"
|
|
10
|
+
# Parsed mode when PCS is omitted, invalid, or empty (not the YAML string "light").
|
|
11
|
+
DEFAULT_PREFERS_COLOR_SCHEME_MODE = :light
|
|
12
|
+
MAX_CHART_BACKGROUND_LENGTH = 256
|
|
13
|
+
INVALID_CHART_BACKGROUND = /[\x00-\x1f"'<>;`\\]/
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
# YAML keys under mermaid_prebuild — hyphenated to align with CSS (@media (prefers-color-scheme), background-color).
|
|
16
|
+
PREFERS_COLOR_SCHEME_YAML_KEY = "prefers-color-scheme"
|
|
17
|
+
BACKGROUND_COLOR_YAML_KEY = "background-color"
|
|
18
|
+
|
|
19
|
+
attr_reader :output_dir, :text_centering, :overflow_protection, :edge_label_padding, :emoji_width_compensation,
|
|
20
|
+
:prefers_color_scheme, :chart_background_light, :chart_background_dark
|
|
10
21
|
|
|
11
22
|
# Initialize configuration from Jekyll site
|
|
12
23
|
#
|
|
@@ -15,6 +26,8 @@ module JekyllMermaidPrebuild
|
|
|
15
26
|
config = site.config["mermaid_prebuild"] || {}
|
|
16
27
|
@output_dir = parse_output_dir(config["output_dir"])
|
|
17
28
|
@enabled = config.fetch("enabled", true)
|
|
29
|
+
pcs_raw = config[PREFERS_COLOR_SCHEME_YAML_KEY]
|
|
30
|
+
parse_prefers_color_scheme(pcs_raw)
|
|
18
31
|
|
|
19
32
|
pp = config["postprocessing"] || {}
|
|
20
33
|
@text_centering = pp.fetch("text_centering", true)
|
|
@@ -39,6 +52,116 @@ module JekyllMermaidPrebuild
|
|
|
39
52
|
|
|
40
53
|
private
|
|
41
54
|
|
|
55
|
+
# Parse the prefers-color-scheme block (see PREFERS_COLOR_SCHEME_YAML_KEY) from a Hash only
|
|
56
|
+
# (mode + optional background-color map). Non-Hash values fall back to :light and default
|
|
57
|
+
# backgrounds with a warning.
|
|
58
|
+
#
|
|
59
|
+
# @param value [Object] raw site config value
|
|
60
|
+
# @return [void]
|
|
61
|
+
def parse_prefers_color_scheme(value)
|
|
62
|
+
unless value.is_a?(Hash)
|
|
63
|
+
@prefers_color_scheme = DEFAULT_PREFERS_COLOR_SCHEME_MODE
|
|
64
|
+
@chart_background_light = finalize_background(DEFAULT_CHART_BG_LIGHT)
|
|
65
|
+
@chart_background_dark = finalize_background(DEFAULT_CHART_BG_DARK)
|
|
66
|
+
unless value.nil?
|
|
67
|
+
Jekyll.logger.warn(
|
|
68
|
+
"MermaidPrebuild:",
|
|
69
|
+
"Invalid #{PREFERS_COLOR_SCHEME_YAML_KEY} (expected a Hash); " \
|
|
70
|
+
"using light mode and default backgrounds"
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
return
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
mode_raw = config_hash_fetch(value, "mode")
|
|
77
|
+
@prefers_color_scheme = normalize_prefers_mode(mode_raw)
|
|
78
|
+
|
|
79
|
+
bg_container = config_hash_fetch(value, BACKGROUND_COLOR_YAML_KEY)
|
|
80
|
+
if bg_container.is_a?(Hash)
|
|
81
|
+
light_raw = config_hash_fetch(bg_container, "light")
|
|
82
|
+
dark_raw = config_hash_fetch(bg_container, "dark")
|
|
83
|
+
@chart_background_light = coerce_chart_background(light_raw, DEFAULT_CHART_BG_LIGHT, "light")
|
|
84
|
+
@chart_background_dark = coerce_chart_background(dark_raw, DEFAULT_CHART_BG_DARK, "dark")
|
|
85
|
+
else
|
|
86
|
+
@chart_background_light = finalize_background(DEFAULT_CHART_BG_LIGHT)
|
|
87
|
+
@chart_background_dark = finalize_background(DEFAULT_CHART_BG_DARK)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @param raw [Object]
|
|
92
|
+
# @return [Symbol] :light, :dark, or :auto
|
|
93
|
+
def normalize_prefers_mode(raw)
|
|
94
|
+
return DEFAULT_PREFERS_COLOR_SCHEME_MODE if raw.nil?
|
|
95
|
+
|
|
96
|
+
s = raw.to_s.strip.downcase
|
|
97
|
+
return DEFAULT_PREFERS_COLOR_SCHEME_MODE if s.empty?
|
|
98
|
+
|
|
99
|
+
case s
|
|
100
|
+
when "light" then :light
|
|
101
|
+
when "dark" then :dark
|
|
102
|
+
when "auto" then :auto
|
|
103
|
+
else
|
|
104
|
+
Jekyll.logger.warn(
|
|
105
|
+
"MermaidPrebuild:",
|
|
106
|
+
"Invalid #{PREFERS_COLOR_SCHEME_YAML_KEY} mode #{raw.inspect}; using light"
|
|
107
|
+
)
|
|
108
|
+
DEFAULT_PREFERS_COLOR_SCHEME_MODE
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Read a config key from a Hash (string key as in YAML, or matching Symbol).
|
|
113
|
+
#
|
|
114
|
+
# @param hash [Hash]
|
|
115
|
+
# @param key [String] exact key (e.g. "mode", "background-color", "light")
|
|
116
|
+
# @return [Object, nil]
|
|
117
|
+
def config_hash_fetch(hash, key)
|
|
118
|
+
return nil unless hash.is_a?(Hash)
|
|
119
|
+
|
|
120
|
+
return hash[key] if hash.key?(key)
|
|
121
|
+
return hash[key.to_sym] if hash.key?(key.to_sym)
|
|
122
|
+
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# @param value [Object] raw color string or nil (use default)
|
|
127
|
+
# @param default [String] fallback literal
|
|
128
|
+
# @param label [String] "light" or "dark" for logging
|
|
129
|
+
# @return [String] frozen sanitized CSS fragment
|
|
130
|
+
def coerce_chart_background(value, default, label)
|
|
131
|
+
return finalize_background(default) if value.nil?
|
|
132
|
+
|
|
133
|
+
str = value.to_s.strip
|
|
134
|
+
if str.empty?
|
|
135
|
+
Jekyll.logger.warn "MermaidPrebuild:",
|
|
136
|
+
"Invalid chart background (#{label}): empty string; using #{default.inspect}"
|
|
137
|
+
return finalize_background(default)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
if str.length > MAX_CHART_BACKGROUND_LENGTH
|
|
141
|
+
Jekyll.logger.warn "MermaidPrebuild:",
|
|
142
|
+
"Invalid chart background (#{label}): value too long; using #{default.inspect}"
|
|
143
|
+
return finalize_background(default)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if chart_background_invalid?(str)
|
|
147
|
+
Jekyll.logger.warn "MermaidPrebuild:",
|
|
148
|
+
"Invalid chart background (#{label}): disallowed characters; using #{default.inspect}"
|
|
149
|
+
return finalize_background(default)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
str.freeze
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def chart_background_invalid?(value)
|
|
156
|
+
INVALID_CHART_BACKGROUND.match?(value)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# @param value [String]
|
|
160
|
+
# @return [String] frozen copy
|
|
161
|
+
def finalize_background(value)
|
|
162
|
+
value.to_s.freeze
|
|
163
|
+
end
|
|
164
|
+
|
|
42
165
|
# Returns a frozen Hash of diagram type (string) => boolean. Non-hash values are rejected → {}.
|
|
43
166
|
#
|
|
44
167
|
# @param value [Object] raw config value
|
|
@@ -19,28 +19,21 @@ module JekyllMermaidPrebuild
|
|
|
19
19
|
# @param mermaid_source [String] mermaid diagram definition
|
|
20
20
|
# @param cache_key [String] digest for caching
|
|
21
21
|
# @param diagram_type [String, nil] from EmojiCompensator.detect_diagram_type (e.g. "block")
|
|
22
|
-
# @return [String, nil]
|
|
22
|
+
# @return [Hash{String => String}, nil] stem (cache filename without .svg) => absolute cache path, or nil on failure
|
|
23
23
|
def generate(mermaid_source, cache_key, diagram_type: nil)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# Generate SVG using mmdc
|
|
33
|
-
success = MmdcWrapper.render(mermaid_source, cache_path)
|
|
34
|
-
return nil unless success
|
|
35
|
-
|
|
36
|
-
post_process_svg(cache_path, diagram_type)
|
|
37
|
-
|
|
38
|
-
cache_path
|
|
24
|
+
case @config.prefers_color_scheme
|
|
25
|
+
when :light
|
|
26
|
+
generate_one(mermaid_source, cache_key, diagram_type: diagram_type, theme: :default)
|
|
27
|
+
when :dark
|
|
28
|
+
generate_one(mermaid_source, cache_key, diagram_type: diagram_type, theme: :dark)
|
|
29
|
+
when :auto
|
|
30
|
+
generate_auto(mermaid_source, cache_key, diagram_type: diagram_type)
|
|
31
|
+
end
|
|
39
32
|
end
|
|
40
33
|
|
|
41
|
-
# Build SVG URL from cache key
|
|
34
|
+
# Build SVG URL from cache key (stem, e.g. digest or digest-dark)
|
|
42
35
|
#
|
|
43
|
-
# @param cache_key [String] the cache
|
|
36
|
+
# @param cache_key [String] the cache stem
|
|
44
37
|
# @return [String] URL path to SVG
|
|
45
38
|
def build_svg_url(cache_key)
|
|
46
39
|
"/#{@config.output_dir}/#{cache_key}.svg"
|
|
@@ -48,9 +41,27 @@ module JekyllMermaidPrebuild
|
|
|
48
41
|
|
|
49
42
|
# Build figure HTML for a diagram
|
|
50
43
|
#
|
|
51
|
-
# @param svg_url [String] URL to the SVG file
|
|
44
|
+
# @param svg_url [String] URL to the light (or only) SVG file
|
|
45
|
+
# @param dark_url [String, nil] when set (auto mode), second link + CSS toggle for prefers-color-scheme
|
|
52
46
|
# @return [String] HTML figure element
|
|
53
|
-
def build_figure_html(svg_url)
|
|
47
|
+
def build_figure_html(svg_url, dark_url: nil)
|
|
48
|
+
if dark_url
|
|
49
|
+
return <<~HTML
|
|
50
|
+
<figure class="mermaid-diagram">
|
|
51
|
+
<style>
|
|
52
|
+
.mermaid-diagram__light { display: inline; }
|
|
53
|
+
.mermaid-diagram__dark { display: none; }
|
|
54
|
+
@media (prefers-color-scheme: dark) {
|
|
55
|
+
.mermaid-diagram__light { display: none; }
|
|
56
|
+
.mermaid-diagram__dark { display: inline; }
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
59
|
+
<a class="mermaid-diagram__light" href="#{svg_url}"><img src="#{svg_url}" alt="Mermaid Diagram"></a>
|
|
60
|
+
<a class="mermaid-diagram__dark" href="#{dark_url}"><img src="#{dark_url}" alt="Mermaid Diagram"></a>
|
|
61
|
+
</figure>
|
|
62
|
+
HTML
|
|
63
|
+
end
|
|
64
|
+
|
|
54
65
|
<<~HTML
|
|
55
66
|
<figure class="mermaid-diagram">
|
|
56
67
|
<a href="#{svg_url}"><img src="#{svg_url}" alt="Mermaid Diagram"></a>
|
|
@@ -60,12 +71,50 @@ module JekyllMermaidPrebuild
|
|
|
60
71
|
|
|
61
72
|
private
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
# @return [Hash{String => String}, nil]
|
|
75
|
+
def generate_one(mermaid_source, stem, diagram_type:, theme:)
|
|
76
|
+
cache_path = File.join(@config.cache_dir, "#{stem}.svg")
|
|
77
|
+
return { stem => cache_path } if File.exist?(cache_path)
|
|
78
|
+
|
|
79
|
+
FileUtils.mkdir_p(@config.cache_dir)
|
|
80
|
+
return nil unless MmdcWrapper.render(mermaid_source, cache_path, theme: theme)
|
|
81
|
+
|
|
82
|
+
bg = theme == :dark ? @config.chart_background_dark : @config.chart_background_light
|
|
83
|
+
post_process_svg(cache_path, diagram_type, root_background: bg)
|
|
84
|
+
{ stem => cache_path }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @return [Hash{String => String}, nil]
|
|
88
|
+
def generate_auto(mermaid_source, cache_key, diagram_type:)
|
|
89
|
+
light_stem = cache_key
|
|
90
|
+
dark_stem = "#{cache_key}-dark"
|
|
91
|
+
light_path = File.join(@config.cache_dir, "#{light_stem}.svg")
|
|
92
|
+
dark_path = File.join(@config.cache_dir, "#{dark_stem}.svg")
|
|
93
|
+
|
|
94
|
+
FileUtils.mkdir_p(@config.cache_dir)
|
|
95
|
+
|
|
96
|
+
unless File.exist?(light_path)
|
|
97
|
+
return nil unless MmdcWrapper.render(mermaid_source, light_path, theme: :default)
|
|
98
|
+
|
|
99
|
+
post_process_svg(light_path, diagram_type, root_background: @config.chart_background_light)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
unless File.exist?(dark_path)
|
|
103
|
+
return nil unless MmdcWrapper.render(mermaid_source, dark_path, theme: :dark)
|
|
104
|
+
|
|
105
|
+
post_process_svg(dark_path, diagram_type, root_background: @config.chart_background_dark)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
{ light_stem => light_path, dark_stem => dark_path }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def post_process_svg(cache_path, _diagram_type, root_background:)
|
|
64
112
|
raw = File.read(cache_path)
|
|
65
113
|
svg = raw
|
|
66
114
|
|
|
67
115
|
svg = SvgPostProcessor.ensure_text_centering(svg) if @config.text_centering
|
|
68
116
|
svg = SvgPostProcessor.ensure_foreignobject_overflow(svg) if @config.overflow_protection
|
|
117
|
+
svg = SvgPostProcessor.apply_root_svg_background(svg, root_background)
|
|
69
118
|
|
|
70
119
|
pad = @config.edge_label_padding
|
|
71
120
|
svg = SvgPostProcessor.apply(svg, padding: pad) if pad.is_a?(Numeric) && pad.positive?
|
|
@@ -95,8 +95,17 @@ module JekyllMermaidPrebuild
|
|
|
95
95
|
#
|
|
96
96
|
# @param mermaid_source [String] mermaid diagram definition
|
|
97
97
|
# @param output_path [String] path to write SVG file
|
|
98
|
+
ALLOWED_RENDER_THEMES = %i[default dark].freeze
|
|
99
|
+
|
|
100
|
+
# @param theme [Symbol] :default (mermaid default theme) or :dark (mmdc -t dark)
|
|
98
101
|
# @return [Boolean] true if successful
|
|
99
|
-
|
|
102
|
+
# @raise [ArgumentError] if theme is not supported
|
|
103
|
+
def render(mermaid_source, output_path, theme: :default)
|
|
104
|
+
unless ALLOWED_RENDER_THEMES.include?(theme)
|
|
105
|
+
raise ArgumentError,
|
|
106
|
+
"unsupported mmdc theme #{theme.inspect} (allowed: #{ALLOWED_RENDER_THEMES.map(&:inspect).join(", ")})"
|
|
107
|
+
end
|
|
108
|
+
|
|
100
109
|
input_file = Tempfile.new(["mermaid", ".mmd"])
|
|
101
110
|
|
|
102
111
|
begin
|
|
@@ -104,6 +113,7 @@ module JekyllMermaidPrebuild
|
|
|
104
113
|
input_file.close
|
|
105
114
|
|
|
106
115
|
cmd = ["mmdc", "-i", input_file.path, "-o", output_path, "-e", "svg"]
|
|
116
|
+
cmd += ["-t", "dark"] if theme == :dark
|
|
107
117
|
_stdout, _stderr, status = Open3.capture3(*cmd)
|
|
108
118
|
|
|
109
119
|
status.success?
|
|
@@ -37,7 +37,7 @@ module JekyllMermaidPrebuild
|
|
|
37
37
|
next unless result
|
|
38
38
|
|
|
39
39
|
converted_count += 1
|
|
40
|
-
svgs_to_copy
|
|
40
|
+
svgs_to_copy.merge!(result[:svgs])
|
|
41
41
|
processed[block[:start]...block[:end]] = result[:html]
|
|
42
42
|
end
|
|
43
43
|
|
|
@@ -51,6 +51,9 @@ module JekyllMermaidPrebuild
|
|
|
51
51
|
# @return [String] input to MD5 for cache key
|
|
52
52
|
def digest_string_for_cache(source, _diagram_type)
|
|
53
53
|
parts = [source]
|
|
54
|
+
parts << "pcs=#{@config.prefers_color_scheme}"
|
|
55
|
+
parts << "bgL=#{@config.chart_background_light}"
|
|
56
|
+
parts << "bgD=#{@config.chart_background_dark}"
|
|
54
57
|
parts << "tc=#{@config.text_centering}" unless @config.text_centering
|
|
55
58
|
parts << "op=#{@config.overflow_protection}" unless @config.overflow_protection
|
|
56
59
|
pad = @config.edge_label_padding
|
|
@@ -61,7 +64,7 @@ module JekyllMermaidPrebuild
|
|
|
61
64
|
# Convert a single mermaid block to SVG
|
|
62
65
|
#
|
|
63
66
|
# @param block [Hash] block info with :content key
|
|
64
|
-
# @return [Hash, nil] {
|
|
67
|
+
# @return [Hash, nil] {:svgs, :html} or nil if failed
|
|
65
68
|
def convert_block(block)
|
|
66
69
|
mermaid_source = block[:content]
|
|
67
70
|
diagram_type = EmojiCompensator.detect_diagram_type(mermaid_source)
|
|
@@ -72,12 +75,19 @@ module JekyllMermaidPrebuild
|
|
|
72
75
|
end
|
|
73
76
|
digest_input = digest_string_for_cache(source_for_render, diagram_type)
|
|
74
77
|
cache_key = DigestCalculator.content_digest(digest_input)
|
|
75
|
-
|
|
78
|
+
paths = @generator.generate(source_for_render, cache_key, diagram_type: diagram_type)
|
|
76
79
|
|
|
77
|
-
return nil
|
|
80
|
+
return nil if paths.nil? || paths.empty?
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
light_url = @generator.build_svg_url(cache_key)
|
|
83
|
+
html = if @config.prefers_color_scheme == :auto
|
|
84
|
+
dark_url = @generator.build_svg_url("#{cache_key}-dark")
|
|
85
|
+
@generator.build_figure_html(light_url, dark_url: dark_url)
|
|
86
|
+
else
|
|
87
|
+
@generator.build_figure_html(light_url)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
{ svgs: paths, html: html }
|
|
81
91
|
end
|
|
82
92
|
|
|
83
93
|
# Find all top-level mermaid code blocks (not nested inside other fences)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module JekyllMermaidPrebuild
|
|
4
|
-
# Post-processes mmdc-generated SVGs to fix
|
|
4
|
+
# Post-processes mmdc-generated SVGs to fix rendering issues.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
6
|
+
# Four independent fixes:
|
|
7
7
|
# 1. Text centering: Mermaid's CSS `text-align: center` targets SVG `<g>` elements where it
|
|
8
8
|
# has no effect on HTML inside `<foreignObject>`. We inject a CSS rule so that foreignObject
|
|
9
9
|
# content centers correctly regardless of text measurement differences between the generating
|
|
@@ -14,6 +14,9 @@ module JekyllMermaidPrebuild
|
|
|
14
14
|
# 3. Edge label padding: Widens edge-label `<foreignObject>` widths in any diagram type to
|
|
15
15
|
# prevent clipping when the viewing browser renders text wider than headless Chromium measured.
|
|
16
16
|
# Opt-in via `postprocessing.edge_label_padding` config.
|
|
17
|
+
# 4. Root SVG background: mmdc always emits `background-color: white` on the root `<svg>` regardless
|
|
18
|
+
# of theme. The plugin replaces that token with configurable CSS color values for light and
|
|
19
|
+
# dark variants (defaults white / black) so charts match page background in both modes.
|
|
17
20
|
module SvgPostProcessor
|
|
18
21
|
module_function
|
|
19
22
|
|
|
@@ -75,6 +78,25 @@ module JekyllMermaidPrebuild
|
|
|
75
78
|
svg_string
|
|
76
79
|
end
|
|
77
80
|
|
|
81
|
+
# Replace `background-color: white` on the root <svg> style attribute with a caller-supplied
|
|
82
|
+
# CSS color (already sanitized by Configuration). Idempotent when mmdc output no longer contains
|
|
83
|
+
# the white token or the value already matches.
|
|
84
|
+
#
|
|
85
|
+
# @param svg_string [String] full SVG document from mmdc
|
|
86
|
+
# @param css_background [String] literal after `background-color:` (e.g. "black", "#fff0aa")
|
|
87
|
+
# @return [String] SVG with updated root background, or original on no-op / error
|
|
88
|
+
def apply_root_svg_background(svg_string, css_background)
|
|
89
|
+
return svg_string unless svg_string.is_a?(String)
|
|
90
|
+
return svg_string unless css_background.is_a?(String) && !css_background.empty?
|
|
91
|
+
|
|
92
|
+
svg_string.sub(
|
|
93
|
+
/(<svg\b[^>]*\bstyle="[^"]*?)background-color:\s*white;?/,
|
|
94
|
+
"\\1background-color: #{css_background};"
|
|
95
|
+
)
|
|
96
|
+
rescue StandardError
|
|
97
|
+
svg_string
|
|
98
|
+
end
|
|
99
|
+
|
|
78
100
|
def apply_edge_label_padding(svg_string, padding)
|
|
79
101
|
svg_string.gsub(EDGE_LABEL_FOREIGN_OBJECT_RE) do
|
|
80
102
|
prefix = Regexp.last_match(1)
|