Pandoc Mermaid Lua フィルタ (mermaid.lua)

1 概要

この Lua フィルタは、Pandoc 文書内の Mermaid コードブロックを自動的に SVG 画像に変換するためのフィルタです。mermaid-cli (mmdc) を使用して Mermaid 記法をレンダリングし、生成された SVG ファイルを最適化して文書に埋め込みます。

2 基本的な動作フロー

2.1 コードブロックの検出と処理

CodeBlock = function(el) 
    local code_class = el.classes[1] or ""
    local lang, filename = code_class:match("^([^:]+):(.+)$")
    if not lang then
        lang = code_class
    end
    -- コード種別判定
    if lang ~= "mermaid" then
        return el
    end
  • コードブロックのクラス属性を解析
  • mermaid または mermaid:filename 形式をサポート
  • mermaid 以外のコードブロックはそのまま通過

2.2 キャプション処理

-- caption 属性があれば優先してキャプションに
local caption = el.attributes["caption"]
if caption then
    el.attributes["caption"] = nil
elseif filename then
    filename = filename:gsub("%.[mM][mM][dD]$", "")
    caption = filename
end
  • caption 属性が指定されていればそれを使用
  • なければファイル名部分 (拡張子除く) をキャプションとして使用

2.3 クロスプラットフォーム対応

2.3.1 Windows 環境での文字エンコーディング処理

function utf8_to_active_cp(text)
    local os_name = os.getenv("OS")
    if not os_name or not string.match(os_name:lower(), "windows") then
        return text  -- Linux環境ではそのまま
    end
    
    -- PowerShellを使用してUTF-8からアクティブコードページに変換
    local temp_file = create_temp_file()
    -- ... PowerShell経由でのエンコーディング変換処理
end
  • Windows 環境でのファイルパス文字化け対策
  • PowerShell を使用して UTF-8 文字列をシステムのアクティブコードページに変換

2.3.2 実行コマンドの選択

local MMDC_CMD
if package.config:sub(1,1) == '\\' then -- Windows
    MMDC_CMD = "\\node_modules\\.bin\\mmdc.cmd"
else -- Unix-like systems
    MMDC_CMD = "/mmdc-wrapper.sh"
end

2.4 ファイル生成処理

local image_filename = string.format("mermaid_%s.svg", utils.sha1(el.text))
local mmd_filename = string.format("mermaid_%s.mmd", utils.sha1(el.text))
  • SHA1 ハッシュを使用してユニークなファイル名を生成
  • 同じ内容の Mermaid コードは同じファイル名になり、重複処理を回避

2.5 Mermaid-CLI 実行

os.execute(string.format("cd %s && \"%s\" -i %s -o %s -b transparent | grep -v -E \"Generating|deprecated|Store is a function\"", 
    _resource_dir, _root_dir .. MMDC_CMD, _mmd_filename, _image_filename))
  • 一時的な .mmd ファイルを作成
  • mmdc (mermaid-cli) を実行してSVGを生成
  • 背景を透明に設定 (-b transparent)
  • 不要な出力メッセージをフィルタリング

3 SVG 補正 (パッチ) 処理の詳細

3.1 問題の背景

Mermaid-CLI で生成される SVG には以下の問題がある:

  1. サイズ指定が不適切: width="100%" で出力される
  2. スタイル属性の問題: max-width が設定されているが、固定サイズが必要な場面で問題となる

3.2 補正処理の実装

width="100%" による予期しないスケーリングを防止する。

3.2.1 ViewBox からサイズ情報を抽出

local viewBox = svg_content:match('viewBox="([^"]+)"')
if viewBox then
    local _, _, w, h = viewBox:match("([%-%d%.]+) ([%-%d%.]+) ([%d%.]+) ([%d%.]+)")
    width = w
    height = h
end

処理内容:

  • SVG の viewBox 属性から実際の描画領域サイズを取得
  • viewBox="-50 -10 485 259" の場合、幅 485px、高さ 259px を抽出

3.2.2 スタイル属性の書き換え

patched_svg = patched_svg:gsub('(<svg[^>]-)style="([^"]*)"', function(svg_tag, style)
    -- max-width を削除
    style = style:gsub("max%-width:[^;]*;? ?", "")
    -- width / height を削除
    style = style:gsub("width:[^;]*;?", "")
    style = style:gsub("height:[^;]*;?", "")
    -- 末尾に width / height 追加
    return string.format('%sstyle="width:%spx; height:%spx; %s"', svg_tag, width, height, style)
end, 1)

処理内容:

  • ルート SVG 要素の style 属性のみを対象 (end, 1 で最初の要素のみ)
  • max-width、既存の widthheight プロパティを削除
  • ViewBox から取得した固定サイズを設定

3.2.3 SVG 属性の書き換え

patched_svg = patched_svg
    :gsub('(<svg[^>]*)%swidth="[^"]*"', '%1', 1)
    :gsub('(<svg[^>]*)%sheight="[^"]*"', '%1', 1)
    :gsub('(<svg)', '%1 width="' .. width .. 'px" height="' .. height .. 'px"', 1)

処理内容:

  • 既存の widthheight 属性を削除
  • ViewBox から取得したサイズで新しい widthheight 属性を追加

3.3 補正前後の比較

補正前:

<svg aria-roledescription="sequence" role="graphics-document document" 
     viewBox="-50 -10 485 259" 
     style="max-width: 485px; background-color: transparent;" 
     xmlns:xlink="http://www.w3.org/1999/xlink" 
     xmlns="http://www.w3.org/2000/svg" 
     width="100%" 
     id="my-svg">

補正後:

<svg width="485px" height="259px" 
     aria-roledescription="sequence" role="graphics-document document" 
     viewBox="-50 -10 485 259" 
     style="width:485px; height:259px; background-color: transparent;" 
     xmlns:xlink="http://www.w3.org/1999/xlink" 
     xmlns="http://www.w3.org/2000/svg" 
     id="my-svg">