jekyll-mermaid-prebuild 0.3.1 → 0.3.2
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 +17 -0
- data/README.md +18 -1
- data/lib/jekyll-mermaid-prebuild/configuration.rb +13 -1
- data/lib/jekyll-mermaid-prebuild/generator.rb +16 -1
- data/lib/jekyll-mermaid-prebuild/processor.rb +13 -2
- data/lib/jekyll-mermaid-prebuild/svg_post_processor.rb +72 -0
- data/lib/jekyll-mermaid-prebuild/version.rb +1 -1
- data/lib/jekyll-mermaid-prebuild.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 774a2f269fa0af19b0e9dd2008518271984d1a3e812037fa0eb9c27d54b08ed9
|
|
4
|
+
data.tar.gz: ce18c8d08f2fb59ebbffdd02c87e32dccd021bb839650aebbc065fa5778cae0e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bb13f85293b01d89745391543a28885ecc3ff05a9ca4448c5c8929df641cb72801c2efc65c6984d9960b36cea1a92716adeb6c3b66bc12d19013b6b23750dbd4
|
|
7
|
+
data.tar.gz: cec8771614670a55c05b0690db77d6914952a5354362aa402296d96caea841ee3647f4a3c64d39145e18bf0cb45bc6d6d82d0c52696a679c5e8e9b1ca4747a7f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.2](https://github.com/Texarkanine/jekyll-mermaid-prebuild/compare/v0.3.1...v0.3.2) (2026-03-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* block diagram node label centering and clipping w/ latest Mermaid ([#16](https://github.com/Texarkanine/jekyll-mermaid-prebuild/issues/16)) ([ae250c1](https://github.com/Texarkanine/jekyll-mermaid-prebuild/commit/ae250c14fd04d3f1826a70b8af339ced67bb988a))
|
|
9
|
+
|
|
10
|
+
## [Unreleased]
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* Optional `block_edge_label_padding` widens block-diagram edge-label `<foreignObject>` widths after mmdc to prevent clipping caused by cross-browser text measurement differences on non-Mac build hosts.
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* Inject `foreignObject > div { display: block !important; text-align: center }` into every generated SVG so that label text centers correctly when the viewing browser's font metrics differ from the generating headless Chromium. Fixes left-shifted labels visible when mmdc runs on a different OS than the viewer.
|
|
19
|
+
|
|
3
20
|
## [0.3.1](https://github.com/Texarkanine/jekyll-mermaid-prebuild/compare/v0.3.0...v0.3.1) (2026-03-13)
|
|
4
21
|
|
|
5
22
|
|
data/README.md
CHANGED
|
@@ -94,6 +94,7 @@ 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
|
+
block_edge_label_padding: 0 # optional; see Block diagrams below
|
|
97
98
|
emoji_width_compensation: # optional, see below
|
|
98
99
|
flowchart: true
|
|
99
100
|
```
|
|
@@ -104,8 +105,23 @@ mermaid_prebuild:
|
|
|
104
105
|
|--------|---------|-------------|
|
|
105
106
|
| `enabled` | `true` | Enable/disable the plugin |
|
|
106
107
|
| `output_dir` | `assets/svg` | Directory for generated SVG files |
|
|
108
|
+
| `block_edge_label_padding` | `0` | Extra SVG user units added to **block** diagram edge-label `<foreignObject>` widths after mmdc (off when `0`, `false`, or omitted). See [Cross-browser text rendering fixes](#cross-browser-text-rendering-fixes). |
|
|
107
109
|
| `emoji_width_compensation` | `{}` | Map of diagram types to booleans; see [Emoji width compensation](#emoji-width-compensation) below. |
|
|
108
110
|
|
|
111
|
+
### Cross-browser text rendering fixes
|
|
112
|
+
|
|
113
|
+
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 applies two automatic fixes to every generated SVG:
|
|
114
|
+
|
|
115
|
+
1. **Text centering** (always on, no config needed): Mermaid's CSS sets `text-align: center` on SVG `<g>` elements, but that has no effect on HTML inside `<foreignObject>`. The plugin injects a CSS rule (`foreignObject > div { display: block !important; text-align: center }`) so that label text centers within its container regardless of font metric differences. This is idempotent — if upstream Mermaid fixes this, the rule becomes redundant but harmless.
|
|
116
|
+
|
|
117
|
+
2. **Block edge label padding** (opt-in via `block_edge_label_padding`): Block diagram edge labels have zero padding between the `<foreignObject>` boundary and the text. If the viewing browser renders text wider than headless Chromium measured, the last character(s) clip. This option widens only **edge** label `<foreignObject>` elements (not node labels) in SVGs whose root has `aria-roledescription="block"`. Flowcharts and other diagram types are unchanged.
|
|
118
|
+
|
|
119
|
+
- **When to enable:** If block diagram edge text clips in generated SVGs on your build host.
|
|
120
|
+
- **Starting value:** Try `4`-`8` (SVG user units); increase only if needed.
|
|
121
|
+
- **Caching:** The cache key includes this padding for block diagrams only, so changing the value invalidates cached block SVGs without affecting flowcharts.
|
|
122
|
+
|
|
123
|
+
> **CI tip:** If your CI pipeline sets `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true` and `PUPPETEER_EXECUTABLE_PATH` to the runner's system Chrome, remove those overrides. Puppeteer's bundled Chromium produces measurements closer to common viewing browsers than the GitHub Actions runner's system Chrome, which can measure text 10-16% narrower.
|
|
124
|
+
|
|
109
125
|
### Emoji width compensation
|
|
110
126
|
|
|
111
127
|
Headless Chromium (used by mermaid-cli/mmdc) [undermeasures emoji glyph widths](https://stackoverflow.com/q/42016125) on non-Mac platforms. That can make node labels containing emoji clip in the generated SVG. This option tells the plugin to **append invisible ` ` padding** to emoji-containing node labels *before* passing the source to mmdc, so Puppeteer allocates correct widths.
|
|
@@ -144,12 +160,13 @@ mermaid_prebuild:
|
|
|
144
160
|
|
|
145
161
|
## Caching
|
|
146
162
|
|
|
147
|
-
Generated SVGs are cached in `.jekyll-cache/jekyll-mermaid-prebuild/`. The cache key is based on the diagram content (and, when emoji compensation is enabled for that diagram type, the compensated source), so:
|
|
163
|
+
Generated SVGs are cached in `.jekyll-cache/jekyll-mermaid-prebuild/`. The cache key is based on the diagram content (and, when emoji compensation is enabled for that diagram type, the compensated source; when `block_edge_label_padding` is positive, block diagrams also mix in that value), so:
|
|
148
164
|
|
|
149
165
|
- Unchanged diagrams are served from cache (fast rebuilds)
|
|
150
166
|
- Modified diagrams are automatically regenerated
|
|
151
167
|
- Different diagrams with different content get different cache keys
|
|
152
168
|
- Enabling or disabling emoji width compensation for a diagram type invalidates cache for that content (keys include compensated source when applicable)
|
|
169
|
+
- Changing `block_edge_label_padding` invalidates cache keys for **block** diagrams only
|
|
153
170
|
|
|
154
171
|
To clear the cache:
|
|
155
172
|
|
|
@@ -6,7 +6,7 @@ module JekyllMermaidPrebuild
|
|
|
6
6
|
DEFAULT_OUTPUT_DIR = "assets/svg"
|
|
7
7
|
CACHE_DIR = ".jekyll-cache/jekyll-mermaid-prebuild"
|
|
8
8
|
|
|
9
|
-
attr_reader :output_dir, :emoji_width_compensation
|
|
9
|
+
attr_reader :output_dir, :emoji_width_compensation, :block_edge_label_padding
|
|
10
10
|
|
|
11
11
|
# Initialize configuration from Jekyll site
|
|
12
12
|
#
|
|
@@ -16,6 +16,7 @@ module JekyllMermaidPrebuild
|
|
|
16
16
|
@output_dir = parse_output_dir(config["output_dir"])
|
|
17
17
|
@enabled = config.fetch("enabled", true)
|
|
18
18
|
@emoji_width_compensation = parse_emoji_width_compensation(config["emoji_width_compensation"])
|
|
19
|
+
@block_edge_label_padding = parse_block_edge_label_padding(config["block_edge_label_padding"])
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
# Check if the plugin is enabled
|
|
@@ -54,5 +55,16 @@ module JekyllMermaidPrebuild
|
|
|
54
55
|
# Strip leading/trailing slashes for consistency
|
|
55
56
|
dir.gsub(%r{^/+|/+$}, "")
|
|
56
57
|
end
|
|
58
|
+
|
|
59
|
+
# @param value [Object] raw config (numeric or off)
|
|
60
|
+
# @return [Numeric] non-negative padding in SVG user units; 0 means disabled
|
|
61
|
+
def parse_block_edge_label_padding(value)
|
|
62
|
+
return 0 if value.nil? || value == false
|
|
63
|
+
|
|
64
|
+
num = value.is_a?(Numeric) ? value : nil
|
|
65
|
+
return 0 unless num
|
|
66
|
+
|
|
67
|
+
num.negative? ? 0 : num
|
|
68
|
+
end
|
|
57
69
|
end
|
|
58
70
|
end
|
|
@@ -18,8 +18,9 @@ module JekyllMermaidPrebuild
|
|
|
18
18
|
#
|
|
19
19
|
# @param mermaid_source [String] mermaid diagram definition
|
|
20
20
|
# @param cache_key [String] digest for caching
|
|
21
|
+
# @param diagram_type [String, nil] from EmojiCompensator.detect_diagram_type (e.g. "block")
|
|
21
22
|
# @return [String, nil] path to cached SVG file or nil on failure
|
|
22
|
-
def generate(mermaid_source, cache_key)
|
|
23
|
+
def generate(mermaid_source, cache_key, diagram_type: nil)
|
|
23
24
|
cache_path = File.join(@config.cache_dir, "#{cache_key}.svg")
|
|
24
25
|
|
|
25
26
|
# Return cached file if it exists
|
|
@@ -32,6 +33,8 @@ module JekyllMermaidPrebuild
|
|
|
32
33
|
success = MmdcWrapper.render(mermaid_source, cache_path)
|
|
33
34
|
return nil unless success
|
|
34
35
|
|
|
36
|
+
post_process_svg(cache_path, diagram_type)
|
|
37
|
+
|
|
35
38
|
cache_path
|
|
36
39
|
end
|
|
37
40
|
|
|
@@ -54,5 +57,17 @@ module JekyllMermaidPrebuild
|
|
|
54
57
|
</figure>
|
|
55
58
|
HTML
|
|
56
59
|
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def post_process_svg(cache_path, diagram_type)
|
|
64
|
+
raw = File.read(cache_path)
|
|
65
|
+
svg = SvgPostProcessor.ensure_text_centering(raw)
|
|
66
|
+
|
|
67
|
+
pad = @config.block_edge_label_padding
|
|
68
|
+
svg = SvgPostProcessor.apply(svg, padding: pad) if diagram_type == "block" && pad.is_a?(Numeric) && pad.positive?
|
|
69
|
+
|
|
70
|
+
File.write(cache_path, svg) if svg != raw
|
|
71
|
+
end
|
|
57
72
|
end
|
|
58
73
|
end
|
|
@@ -46,6 +46,16 @@ module JekyllMermaidPrebuild
|
|
|
46
46
|
|
|
47
47
|
private
|
|
48
48
|
|
|
49
|
+
# @param source [String] mermaid passed to mmdc (after optional emoji compensation)
|
|
50
|
+
# @param diagram_type [String, nil]
|
|
51
|
+
# @return [String] input to MD5 for cache key
|
|
52
|
+
def digest_string_for_cache(source, diagram_type)
|
|
53
|
+
pad = @config.block_edge_label_padding
|
|
54
|
+
return "#{source}\0block_edge_pad=#{pad}" if diagram_type == "block" && pad.is_a?(Numeric) && pad.positive?
|
|
55
|
+
|
|
56
|
+
source
|
|
57
|
+
end
|
|
58
|
+
|
|
49
59
|
# Convert a single mermaid block to SVG
|
|
50
60
|
#
|
|
51
61
|
# @param block [Hash] block info with :content key
|
|
@@ -58,8 +68,9 @@ module JekyllMermaidPrebuild
|
|
|
58
68
|
else
|
|
59
69
|
mermaid_source
|
|
60
70
|
end
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
digest_input = digest_string_for_cache(source_for_render, diagram_type)
|
|
72
|
+
cache_key = DigestCalculator.content_digest(digest_input)
|
|
73
|
+
cached_path = @generator.generate(source_for_render, cache_key, diagram_type: diagram_type)
|
|
63
74
|
|
|
64
75
|
return nil unless cached_path
|
|
65
76
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JekyllMermaidPrebuild
|
|
4
|
+
# Post-processes mmdc-generated SVGs to fix cross-browser rendering issues.
|
|
5
|
+
#
|
|
6
|
+
# Two independent fixes:
|
|
7
|
+
# 1. Text centering: Mermaid's CSS `text-align: center` targets SVG `<g>` elements where it
|
|
8
|
+
# has no effect on HTML inside `<foreignObject>`. We inject a CSS rule so that foreignObject
|
|
9
|
+
# content centers correctly regardless of text measurement differences between the generating
|
|
10
|
+
# and viewing browsers. Always applied, idempotent.
|
|
11
|
+
# 2. Block edge label padding: Widens block-diagram edge-label `<foreignObject>` widths to
|
|
12
|
+
# prevent clipping when the viewing browser renders text wider than headless Chromium measured.
|
|
13
|
+
# Opt-in via `block_edge_label_padding` config.
|
|
14
|
+
module SvgPostProcessor
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# Opening sequence produced by mmdc for block edge labels (deterministic minified output).
|
|
18
|
+
EDGE_LABEL_FOREIGN_OBJECT_RE = /
|
|
19
|
+
(<g\sclass="edgeLabel"[^>]*><g\sclass="label"[^>]*><foreignObject)
|
|
20
|
+
(\s[^>]+)
|
|
21
|
+
(>)
|
|
22
|
+
/x
|
|
23
|
+
|
|
24
|
+
BLOCK_ROOT_MARKER = 'aria-roledescription="block"'
|
|
25
|
+
|
|
26
|
+
# @param svg_string [String] full SVG document from mmdc
|
|
27
|
+
# @param padding [Numeric] user units to add to each matching foreignObject width (must be positive)
|
|
28
|
+
# @return [String] possibly widened SVG, or the original string on no-op / error
|
|
29
|
+
def apply(svg_string, padding:)
|
|
30
|
+
return svg_string unless svg_string.is_a?(String)
|
|
31
|
+
return svg_string unless padding.is_a?(Numeric) && padding.positive?
|
|
32
|
+
return svg_string unless svg_string.include?(BLOCK_ROOT_MARKER)
|
|
33
|
+
|
|
34
|
+
apply_edge_label_padding(svg_string, padding)
|
|
35
|
+
rescue StandardError
|
|
36
|
+
svg_string
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
CENTERING_RULE = "foreignObject > div{display:block !important;text-align:center;}"
|
|
40
|
+
|
|
41
|
+
# Injects a CSS rule into the SVG <style> block that centers text inside foreignObject divs.
|
|
42
|
+
# Mermaid's own `.node .label { text-align: center }` targets SVG <g> elements where
|
|
43
|
+
# text-align has no effect; this rule targets the HTML div directly.
|
|
44
|
+
# Idempotent: no visual effect when foreignObject width matches text width.
|
|
45
|
+
#
|
|
46
|
+
# @param svg_string [String] full SVG document from mmdc
|
|
47
|
+
# @return [String] SVG with centering rule injected, or original on no-op / error
|
|
48
|
+
def ensure_text_centering(svg_string)
|
|
49
|
+
return svg_string unless svg_string.is_a?(String)
|
|
50
|
+
return svg_string if svg_string.include?(CENTERING_RULE)
|
|
51
|
+
return svg_string unless svg_string.include?("</style>")
|
|
52
|
+
|
|
53
|
+
svg_string.sub("</style>", "#{CENTERING_RULE}</style>")
|
|
54
|
+
rescue StandardError
|
|
55
|
+
svg_string
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def apply_edge_label_padding(svg_string, padding)
|
|
59
|
+
svg_string.gsub(EDGE_LABEL_FOREIGN_OBJECT_RE) do
|
|
60
|
+
prefix = Regexp.last_match(1)
|
|
61
|
+
attrs = Regexp.last_match(2)
|
|
62
|
+
suffix = Regexp.last_match(3)
|
|
63
|
+
new_attrs = attrs.sub(/\swidth="(\d+(?:\.\d+)?)"/) do
|
|
64
|
+
new_w = Regexp.last_match(1).to_f + padding
|
|
65
|
+
%( width="#{format("%g", new_w)}")
|
|
66
|
+
end
|
|
67
|
+
prefix + new_attrs + suffix
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
private_class_method :apply_edge_label_padding
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -6,6 +6,7 @@ require_relative "jekyll-mermaid-prebuild/version"
|
|
|
6
6
|
require_relative "jekyll-mermaid-prebuild/configuration"
|
|
7
7
|
require_relative "jekyll-mermaid-prebuild/digest_calculator"
|
|
8
8
|
require_relative "jekyll-mermaid-prebuild/emoji_compensator"
|
|
9
|
+
require_relative "jekyll-mermaid-prebuild/svg_post_processor"
|
|
9
10
|
require_relative "jekyll-mermaid-prebuild/mmdc_wrapper"
|
|
10
11
|
require_relative "jekyll-mermaid-prebuild/generator"
|
|
11
12
|
require_relative "jekyll-mermaid-prebuild/processor"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-mermaid-prebuild
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Texarkanine
|
|
@@ -147,6 +147,7 @@ files:
|
|
|
147
147
|
- lib/jekyll-mermaid-prebuild/hooks.rb
|
|
148
148
|
- lib/jekyll-mermaid-prebuild/mmdc_wrapper.rb
|
|
149
149
|
- lib/jekyll-mermaid-prebuild/processor.rb
|
|
150
|
+
- lib/jekyll-mermaid-prebuild/svg_post_processor.rb
|
|
150
151
|
- lib/jekyll-mermaid-prebuild/version.rb
|
|
151
152
|
homepage: https://github.com/Texarkanine/jekyll-mermaid-prebuild
|
|
152
153
|
licenses:
|