Skip to content

Commit

Permalink
mermaid-cli support (#6)
Browse files Browse the repository at this point in the history
* support for mermaid if mermaid-cli is installed
  • Loading branch information
as-op authored Jun 24, 2024
1 parent 0b12221 commit 90eca7c
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,9 @@ Style/HashTransformValues:
RSpec/ExampleLength:
Enabled: false

RSpec/AnyInstance:
Enabled: false

Naming/VariableNumber:
Enabled: false

Expand Down
24 changes: 23 additions & 1 deletion demo/demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,29 @@ With a reference later in the document defining the URL location.

---

# Mermaid

if https://github.com/mermaid-js/mermaid-cli is installed, the following code block with mermaid fence is converted to and included as PNG

```mermaid
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```

---

# Frontmatter

Frontmatter YML is supported:
Expand Down Expand Up @@ -389,4 +412,3 @@ Duplicated footnote reference[^second].

[^second]: Footnote text.

---
67 changes: 67 additions & 0 deletions lib/md_to_pdf/elements/codeblock.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
require "open3"
require "tempfile"

module MarkdownToPDF
module Codeblock
def draw_codeblock(node, opts)
return if node.fence_info == "mermaid" && build_mermaid_block(node, opts)

cell_style_opts = codeblock_style(opts)
table_rows = build_codeblock_cell_rows(node, cell_style_opts)
return if table_rows.empty?
Expand All @@ -22,6 +27,68 @@ def draw_codeblock(node, opts)

private

def mermaid_cli_available?
return $mermaid_cli_available if defined?($mermaid_cli_available)

$mermaid_cli_available =
begin
_, status = Open3.capture2e("mmdc", "-V")
status.success?
rescue StandardError
false
end
end

def build_mermaid_block(node, _opts)
return false unless respond_to?(:mermaid_cli_enabled?) && mermaid_cli_enabled?
return false unless mermaid_cli_available?

image_file = Tempfile.new(['mermaid', ".png"])
begin
return false unless run_mermaid_cli(node.string_content, image_file.path, 'png',
{ background: "transparent", theme: "neutral" })

image_obj, image_info = @pdf.build_image_object(image_file.path)
options = {
scale: [@pdf.bounds.width / image_info.width.to_f, 1].min
}
@pdf.embed_image(image_obj, image_info, options)
true
rescue StandardError
false
ensure
image_file.unlink
end
end

def run_mermaid_cli(mmdc, destination, format, options = {})
tmp_file('mermaid', 'mmdc', mmdc) do |filename|
args = ["-i", filename, '-e', format, '-o', destination]
args << '-C' << options[:css_file] if options[:css_file]
args << '-t' << options[:theme] if options[:theme]
args << '-w' << options[:width] if options[:width]
args << '-H' << options[:height] if options[:height]
args << '-s' << options[:scale] if options[:scale]
args << '-b' << options[:background] if options[:background]
args << '-p' << options[:puppeteer_config_file] if options[:puppeteer_config_file]
args << '-c' << options[:config_file] if options[:config_file]
_, status = Open3.capture2e("mmdc", *args)
status.success?
rescue StandardError
false
end
end

def tmp_file(basename, input_ext, code)
source_file = Tempfile.new([basename, ".#{input_ext}"])
begin
File.write(source_file.path, code)
yield source_file.path
ensure
source_file.unlink
end
end

def build_codeblock_cell_rows(node, cell_style_opts)
lines = node.string_content.gsub(' ', Prawn::Text::NBSP).split("\n")
lines.map do |line|
Expand Down
4 changes: 4 additions & 0 deletions lib/md_to_pdf/external.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ def warn(text, element, node)
puts "WARNING: #{text}\nGot #{element} at #{node ? node.source_position.inspect : '?'}\n\n"
end

def mermaid_cli_enabled?
true
end

private

def validate_image(url, node)
Expand Down
28 changes: 28 additions & 0 deletions spec/fixtures/mermaid/mermaid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
```mermaid
gantt
title A Gantt Diagram
dateFormat YYYY-MM-DD
section Section
A task :a1, 2014-01-01, 30d
Another task :after a1, 20d
section Another
Task in Another :2014-01-12, 12d
another task :24d
```

```mermaid
quadrantChart
title Reach and engagement of campaigns
x-axis Low Reach --> High Reach
y-axis Low Engagement --> High Engagement
quadrant-1 We should expand
quadrant-2 Need to promote
quadrant-3 Re-evaluate
quadrant-4 May be improved
Campaign A: [0.3, 0.6]
Campaign B: [0.45, 0.23]
Campaign C: [0.57, 0.69]
Campaign D: [0.78, 0.34]
Campaign E: [0.40, 0.34]
Campaign F: [0.35, 0.78]
```
54 changes: 54 additions & 0 deletions spec/markdown_to_pdf/codeblock_mermaid_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require 'pdf_helpers'

describe MarkdownToPDF::Codeblock do
include_context 'with pdf'

it 'keeps using the code block if mermaid cli is not available' do
allow_any_instance_of(described_class)
.to receive(:mermaid_cli_available?)
.and_return(false)
generator.parse_file('mermaid/mermaid.md')
expect_pdf_images([])
expect_pdf([
{ x: 36.0, y: 744.756, text: "gantt" },
{ x: 36.0, y: 728.884, text: "    title A Gantt Diagram" },
{ x: 36.0, y: 713.012, text: "    dateFormat YYYY-MM-DD" },
{ x: 36.0, y: 697.14, text: "    section Section" },
{ x: 36.0, y: 681.268, text: "        A task          :a1, 2014-01-01, 30d" },
{ x: 36.0, y: 665.396, text: "        Another task    :after a1, 20d" },
{ x: 36.0, y: 649.524, text: "    section Another" },
{ x: 36.0, y: 633.652, text: "        Task in Another :2014-01-12, 12d" },
{ x: 36.0, y: 617.78, text: "        another task    :24d" },
{ x: 36.0, y: 603.908, text: "quadrantChart" },
{ x: 36.0, y: 588.036, text: "    title Reach and engagement of campaigns" },
{ x: 36.0, y: 572.164, text: "    x-axis Low Reach --> High Reach" },
{ x: 36.0, y: 556.292, text: "    y-axis Low Engagement --> High Engagement" },
{ x: 36.0, y: 540.42, text: "    quadrant-1 We should expand" },
{ x: 36.0, y: 524.548, text: "    quadrant-2 Need to promote" },
{ x: 36.0, y: 508.676, text: "    quadrant-3 Re-evaluate" },
{ x: 36.0, y: 492.804, text: "    quadrant-4 May be improved" },
{ x: 36.0, y: 476.932, text: "    Campaign A: [0.3, 0.6]" },
{ x: 36.0, y: 461.06, text: "    Campaign B: [0.45, 0.23]" },
{ x: 36.0, y: 445.188, text: "    Campaign C: [0.57, 0.69]" },
{ x: 36.0, y: 429.316, text: "    Campaign D: [0.78, 0.34]" },
{ x: 36.0, y: 413.444, text: "    Campaign E: [0.40, 0.34]" },
{ x: 36.0, y: 397.572, text: "    Campaign F: [0.35, 0.78]" }])
end

it 'replaces mermaid diagram code block with images' do
allow_any_instance_of(described_class)
.to receive(:mermaid_cli_available?)
.and_return(true)
allow_any_instance_of(described_class)
.to receive(:run_mermaid_cli) do |_caller, _mmdc, destination, _format, _options|
png = Base64.decode64("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==")
File.write(destination, png)
true
end
generator.parse_file('mermaid/mermaid.md')
expect_pdf_images([
{ x: 36.0, y: 755.0, width: 1.0, height: 1.0 },
{ x: 36.0, y: 754.0, width: 1.0, height: 1.0 }])
expect_pdf([])
end
end
4 changes: 4 additions & 0 deletions spec/pdf_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ def render_file(filename)
def hyphenate(text)
text # @hyphens.hyphenate(text)
end

def mermaid_cli_enabled?
true
end
end

RSpec.shared_context 'with pdf' do
Expand Down

0 comments on commit 90eca7c

Please sign in to comment.