asciidoctor-diagram 1.5.18 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +28 -0
  3. data/README.adoc +56 -14
  4. data/examples/features.adoc +2 -2
  5. data/lib/asciidoctor-diagram/a2s/converter.rb +55 -0
  6. data/lib/asciidoctor-diagram/a2s/extension.rb +6 -52
  7. data/lib/asciidoctor-diagram/blockdiag/converter.rb +37 -0
  8. data/lib/asciidoctor-diagram/blockdiag/extension.rb +9 -116
  9. data/lib/asciidoctor-diagram/bpmn/converter.rb +62 -0
  10. data/lib/asciidoctor-diagram/bpmn/extension.rb +14 -0
  11. data/lib/asciidoctor-diagram/bpmn.rb +7 -0
  12. data/lib/asciidoctor-diagram/diagram_converter.rb +19 -0
  13. data/lib/asciidoctor-diagram/diagram_processor.rb +320 -0
  14. data/lib/asciidoctor-diagram/diagram_source.rb +275 -0
  15. data/lib/asciidoctor-diagram/ditaa/converter.rb +86 -0
  16. data/lib/asciidoctor-diagram/ditaa/extension.rb +6 -71
  17. data/lib/asciidoctor-diagram/erd/converter.rb +31 -0
  18. data/lib/asciidoctor-diagram/erd/extension.rb +6 -35
  19. data/lib/asciidoctor-diagram/gnuplot/converter.rb +63 -0
  20. data/lib/asciidoctor-diagram/gnuplot/extension.rb +14 -0
  21. data/lib/asciidoctor-diagram/gnuplot.rb +7 -0
  22. data/lib/asciidoctor-diagram/graphviz/converter.rb +32 -0
  23. data/lib/asciidoctor-diagram/graphviz/extension.rb +6 -35
  24. data/lib/asciidoctor-diagram/http/server.rb +127 -0
  25. data/lib/asciidoctor-diagram/lilypond/converter.rb +54 -0
  26. data/lib/asciidoctor-diagram/lilypond/extension.rb +14 -0
  27. data/lib/asciidoctor-diagram/lilypond.rb +7 -0
  28. data/lib/asciidoctor-diagram/meme/converter.rb +122 -0
  29. data/lib/asciidoctor-diagram/meme/extension.rb +5 -107
  30. data/lib/asciidoctor-diagram/mermaid/converter.rb +178 -0
  31. data/lib/asciidoctor-diagram/mermaid/extension.rb +6 -159
  32. data/lib/asciidoctor-diagram/msc/converter.rb +35 -0
  33. data/lib/asciidoctor-diagram/msc/extension.rb +6 -36
  34. data/lib/asciidoctor-diagram/nomnoml/converter.rb +25 -0
  35. data/lib/asciidoctor-diagram/nomnoml/extension.rb +6 -28
  36. data/lib/asciidoctor-diagram/plantuml/converter.rb +115 -0
  37. data/lib/asciidoctor-diagram/plantuml/extension.rb +10 -119
  38. data/lib/asciidoctor-diagram/shaape/converter.rb +25 -0
  39. data/lib/asciidoctor-diagram/shaape/extension.rb +6 -28
  40. data/lib/asciidoctor-diagram/smcat/converter.rb +44 -0
  41. data/lib/asciidoctor-diagram/smcat/extension.rb +14 -0
  42. data/lib/asciidoctor-diagram/smcat.rb +7 -0
  43. data/lib/asciidoctor-diagram/svgbob/converter.rb +25 -0
  44. data/lib/asciidoctor-diagram/svgbob/extension.rb +6 -28
  45. data/lib/asciidoctor-diagram/syntrax/converter.rb +55 -0
  46. data/lib/asciidoctor-diagram/syntrax/extension.rb +6 -51
  47. data/lib/asciidoctor-diagram/tikz/converter.rb +56 -0
  48. data/lib/asciidoctor-diagram/tikz/extension.rb +6 -60
  49. data/lib/asciidoctor-diagram/umlet/converter.rb +24 -0
  50. data/lib/asciidoctor-diagram/umlet/extension.rb +6 -28
  51. data/lib/asciidoctor-diagram/util/java.rb +1 -1
  52. data/lib/asciidoctor-diagram/util/java_socket.rb +7 -9
  53. data/lib/asciidoctor-diagram/util/which.rb +0 -29
  54. data/lib/asciidoctor-diagram/vega/converter.rb +47 -0
  55. data/lib/asciidoctor-diagram/vega/extension.rb +6 -44
  56. data/lib/asciidoctor-diagram/version.rb +1 -1
  57. data/lib/asciidoctor-diagram/wavedrom/converter.rb +50 -0
  58. data/lib/asciidoctor-diagram/wavedrom/extension.rb +6 -46
  59. data/lib/asciidoctor-diagram.rb +4 -0
  60. data/lib/ditaa-1.3.14.jar +0 -0
  61. data/lib/ditaamini-0.12.jar +0 -0
  62. data/lib/plantuml-1.3.14.jar +0 -0
  63. data/lib/plantuml.jar +0 -0
  64. data/lib/server-1.3.14.jar +0 -0
  65. data/spec/bpmn-example.xml +44 -0
  66. data/spec/bpmn_spec.rb +96 -0
  67. data/spec/gnuplot_spec.rb +478 -0
  68. data/spec/lilypond_spec.rb +151 -0
  69. data/spec/mermaid_spec.rb +33 -1
  70. data/spec/plantuml_spec.rb +89 -0
  71. data/spec/smcat_spec.rb +164 -0
  72. data/spec/test_helper.rb +3 -0
  73. metadata +52 -11
  74. data/lib/asciidoctor-diagram/extensions.rb +0 -568
  75. data/lib/ditaa-1.3.13.jar +0 -0
  76. data/lib/ditaamini-0.11.jar +0 -0
  77. data/lib/plantuml-1.3.13.jar +0 -0
  78. data/lib/server-1.3.13.jar +0 -0
@@ -1,79 +1,14 @@
1
- require 'set'
2
-
3
- require_relative '../extensions'
4
- require_relative '../util/java'
1
+ require_relative 'converter'
2
+ require_relative '../diagram_processor'
5
3
 
6
4
  module Asciidoctor
7
5
  module Diagram
8
- # @private
9
- module Ditaa
10
- OPTIONS = {
11
- 'scale' => lambda { |o, v| o << '--scale' << v if v },
12
- 'tabs' => lambda { |o, v| o << '--tabs' << v if v },
13
- 'background' => lambda { |o, v| o << '--background' << v if v },
14
- 'antialias' => lambda { |o, v| o << '--no-antialias' if v == 'false' },
15
- 'separation' => lambda { |o, v| o << '--no-separation' if v == 'false'},
16
- 'round-corners' => lambda { |o, v| o << '--round-corners' if v == 'true'},
17
- 'shadows' => lambda { |o, v| o << '--no-shadows' if v == 'false'},
18
- 'debug' => lambda { |o, v| o << '--debug' if v == 'true'},
19
- 'fixed-slope' => lambda { |o, v| o << '--fixed-slope' if v == 'true'},
20
- 'transparent' => lambda { |o, v| o << '--transparent' if v == 'true'}
21
- }
22
-
23
- JARS = ['ditaa-1.3.13.jar', 'ditaamini-0.11.jar'].map do |jar|
24
- File.expand_path File.join('../..', jar), File.dirname(__FILE__)
25
- end
26
- Java.classpath.concat JARS
27
-
28
- def self.included(mod)
29
- mod.register_format(:png, :image) do |parent, source|
30
- ditaa(parent, source, 'image/png')
31
- end
32
-
33
- mod.register_format(:svg, :image) do |parent, source|
34
- ditaa(parent, source, 'image/svg+xml')
35
- end
36
- end
37
-
38
- def ditaa(parent, source, mime_type)
39
- Java.load
40
-
41
- global_attributes = parent.document.attributes
42
-
43
- options = []
44
-
45
- OPTIONS.keys.each do |key|
46
- value = source.attributes.delete(key) || global_attributes["ditaa-option-#{key}"]
47
- OPTIONS[key].call(options, value)
48
- end
49
-
50
- options_string = options.join(' ')
51
-
52
- headers = {
53
- 'Accept' => mime_type,
54
- 'X-Options' => options_string
55
- }
56
-
57
- response = Java.send_request(
58
- :url => '/ditaa',
59
- :body => source.to_s,
60
- :headers => headers
61
- )
62
-
63
- unless response[:code] == 200
64
- raise Java.create_error("Ditaa image generation failed", response)
65
- end
66
-
67
- response[:body]
68
- end
69
- end
70
-
71
- class DitaaBlockProcessor < Extensions::DiagramBlockProcessor
72
- include Ditaa
6
+ class DitaaBlockProcessor < DiagramBlockProcessor
7
+ use_converter DitaaConverter
73
8
  end
74
9
 
75
- class DitaaBlockMacroProcessor < Extensions::DiagramBlockMacroProcessor
76
- include Ditaa
10
+ class DitaaBlockMacroProcessor < DiagramBlockMacroProcessor
11
+ use_converter DitaaConverter
77
12
  end
78
13
  end
79
14
  end
@@ -0,0 +1,31 @@
1
+ require_relative '../diagram_converter'
2
+ require_relative '../util/cli_generator'
3
+ require_relative '../util/platform'
4
+
5
+ module Asciidoctor
6
+ module Diagram
7
+ # @private
8
+ class ErdConverter
9
+ include DiagramConverter
10
+ include CliGenerator
11
+
12
+
13
+ def supported_formats
14
+ [:png, :svg]
15
+ end
16
+
17
+ def convert(source, format, options)
18
+ erd_path = source.find_command('erd')
19
+ dot_path = source.find_command('dot', :alt_attrs => ['graphvizdot'])
20
+
21
+ dot_code = generate_stdin(erd_path, format.to_s, source.to_s) do |tool_path, output_path|
22
+ [tool_path, '-o', Platform.native_path(output_path), '-f', 'dot']
23
+ end
24
+
25
+ generate_stdin(dot_path, format.to_s, dot_code) do |tool_path, output_path|
26
+ [tool_path, "-o#{Platform.native_path(output_path)}", "-T#{format.to_s}"]
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,43 +1,14 @@
1
- require_relative '../extensions'
2
- require_relative '../util/cli_generator'
3
- require_relative '../util/platform'
4
- require_relative '../util/which'
1
+ require_relative 'converter'
2
+ require_relative '../diagram_processor'
5
3
 
6
4
  module Asciidoctor
7
5
  module Diagram
8
- # @private
9
- module Erd
10
- include CliGenerator
11
- include Which
12
-
13
- def self.included(mod)
14
- [:png, :svg].each do |f|
15
- mod.register_format(f, :image) do |parent, source|
16
- erd(parent, source, f)
17
- end
18
- end
19
- end
20
-
21
- def erd(parent, source, format)
22
- erd_path = which(parent, 'erd')
23
- dot_path = which(parent, 'dot', :alt_attrs => ['graphvizdot'])
24
-
25
- dot_code = generate_stdin(erd_path, format.to_s, source.to_s) do |tool_path, output_path|
26
- [tool_path, '-o', Platform.native_path(output_path), '-f', 'dot']
27
- end
28
-
29
- generate_stdin(dot_path, format.to_s, dot_code) do |tool_path, output_path|
30
- [tool_path, "-o#{Platform.native_path(output_path)}", "-T#{format.to_s}"]
31
- end
32
- end
33
- end
34
-
35
- class ErdBlockProcessor < Extensions::DiagramBlockProcessor
36
- include Erd
6
+ class ErdBlockProcessor < DiagramBlockProcessor
7
+ use_converter ErdConverter
37
8
  end
38
9
 
39
- class ErdBlockMacroProcessor < Extensions::DiagramBlockMacroProcessor
40
- include Erd
10
+ class ErdBlockMacroProcessor < DiagramBlockMacroProcessor
11
+ use_converter ErdConverter
41
12
  end
42
13
  end
43
14
  end
@@ -0,0 +1,63 @@
1
+ require_relative '../diagram_converter'
2
+ require_relative '../util/cli_generator'
3
+ require_relative '../util/platform'
4
+
5
+ module Asciidoctor
6
+ module Diagram
7
+ # @private
8
+ class GnuplotConverter
9
+ include DiagramConverter
10
+ include CliGenerator
11
+
12
+
13
+ def supported_formats
14
+ [:png, :svg, :gif, :txt, :literal]
15
+ end
16
+
17
+ def collect_options(source, name)
18
+ {
19
+ :width => source.attr('width', nil, name),
20
+ :height => source.attr('height', nil, name),
21
+ :transparent => source.attr('transparent', nil, name),
22
+ :crop => source.attr('crop', nil, name),
23
+ :font => source.attr('font', nil, name),
24
+ :fontscale => source.attr('fontscale', nil, name),
25
+ :background => source.attr('background', nil, name),
26
+ }
27
+ end
28
+
29
+ def convert(source, format, options)
30
+ if format == :txt || format == :literal
31
+ terminal = 'dumb'
32
+ else
33
+ terminal = format.to_s
34
+ end
35
+ code = "set term #{terminal}"
36
+
37
+ width = options[:width]
38
+ height = options[:height]
39
+ code << " size #{width},#{height}" unless width.nil? or height.nil?
40
+
41
+ transparent = options[:transparent]
42
+ code << (transparent ? " transparent" : " notransparent") unless transparent.nil?
43
+
44
+ crop = options[:crop]
45
+ code << (crop ? " crop" : " nocrop") unless crop.nil?
46
+
47
+ font = options[:font]
48
+ code << %( font "#{font}") unless font.nil?
49
+
50
+ font_scale = options[:fontscale]
51
+ code << " fontscale #{font_scale}" unless font_scale.nil?
52
+
53
+ background = options[:background]
54
+ code << %( background "#{background}") unless background.nil?
55
+
56
+ code << "\n"
57
+ code << source.to_s
58
+
59
+ generate_stdin_stdout(source.find_command('gnuplot'), code)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'converter'
2
+ require_relative '../diagram_processor'
3
+
4
+ module Asciidoctor
5
+ module Diagram
6
+ class GnuplotBlockProcessor < DiagramBlockProcessor
7
+ use_converter GnuplotConverter
8
+ end
9
+
10
+ class GnuplotBlockMacroProcessor < DiagramBlockMacroProcessor
11
+ use_converter GnuplotConverter
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ require 'asciidoctor/extensions'
2
+ require_relative 'gnuplot/extension'
3
+
4
+ Asciidoctor::Extensions.register do
5
+ block Asciidoctor::Diagram::GnuplotBlockProcessor, :gnuplot
6
+ block_macro Asciidoctor::Diagram::GnuplotBlockMacroProcessor, :gnuplot
7
+ end
@@ -0,0 +1,32 @@
1
+ require_relative '../diagram_converter'
2
+ require_relative '../util/cli_generator'
3
+ require_relative '../util/platform'
4
+
5
+ module Asciidoctor
6
+ module Diagram
7
+ # @private
8
+ class GraphvizConverter
9
+ include DiagramConverter
10
+ include CliGenerator
11
+
12
+ def supported_formats
13
+ [:png, :pdf, :svg]
14
+ end
15
+
16
+ def collect_options(source, name)
17
+ {:layout => source.attr('layout', nil, name)}
18
+ end
19
+
20
+ def convert(source, format, options)
21
+ generate_stdin(source.find_command('dot', :alt_attrs => ['graphvizdot']), format.to_s, source.to_s) do |tool_path, output_path|
22
+ args = [tool_path, "-o#{Platform.native_path(output_path)}", "-T#{format.to_s}"]
23
+
24
+ layout = options[:layout]
25
+ args << "-K#{layout}" if layout
26
+
27
+ args
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,43 +1,14 @@
1
- require_relative '../extensions'
2
- require_relative '../util/cli_generator'
3
- require_relative '../util/platform'
4
- require_relative '../util/which'
1
+ require_relative 'converter'
2
+ require_relative '../diagram_processor'
5
3
 
6
4
  module Asciidoctor
7
5
  module Diagram
8
- # @private
9
- module Graphviz
10
- include CliGenerator
11
- include Which
12
-
13
- def self.included(mod)
14
- [:png, :pdf, :svg].each do |f|
15
- mod.register_format(f, :image) do |parent, source|
16
- graphviz(parent, source, f)
17
- end
18
- end
19
- end
20
-
21
- def graphviz(parent, source, format)
22
- inherit_prefix = name
23
-
24
- generate_stdin(which(parent, 'dot', :alt_attrs => ['graphvizdot']), format.to_s, source.to_s) do |tool_path, output_path|
25
- args = [tool_path, "-o#{Platform.native_path(output_path)}", "-T#{format.to_s}"]
26
-
27
- layout = source.attr('layout', nil, inherit_prefix)
28
- args << "-K#{layout}" if layout
29
-
30
- args
31
- end
32
- end
33
- end
34
-
35
- class GraphvizBlockProcessor < Extensions::DiagramBlockProcessor
36
- include Graphviz
6
+ class GraphvizBlockProcessor < DiagramBlockProcessor
7
+ use_converter GraphvizConverter
37
8
  end
38
9
 
39
- class GraphvizBlockMacroProcessor < Extensions::DiagramBlockMacroProcessor
40
- include Graphviz
10
+ class GraphvizBlockMacroProcessor < DiagramBlockMacroProcessor
11
+ use_converter GraphvizConverter
41
12
  end
42
13
  end
43
14
  end
@@ -0,0 +1,127 @@
1
+ require 'base64'
2
+ require 'json'
3
+ require 'sinatra/base'
4
+ require 'zlib'
5
+
6
+ require_relative '../diagram_source'
7
+ require_relative '../graphviz/converter'
8
+ require_relative '../util/which'
9
+
10
+ module Asciidoctor
11
+ module Diagram
12
+ class Server < Sinatra::Base
13
+ get '/:type/:format/:source' do
14
+ type = params['type']
15
+ accepts = lambda { |t| params['format'].downcase.to_sym == t }
16
+ raw_source = params['source']
17
+ decoded_source = Base64.urlsafe_decode64(raw_source)
18
+ decompressed_source = Zlib::Inflate.inflate(decoded_source)
19
+ source = decompressed_source
20
+ render_diagram(type, accepts, source, {})
21
+ end
22
+
23
+ post '/' do
24
+ params = JSON.parse(request.body.read)
25
+ type = params['diagram_type']
26
+ accepts = lambda { |t| params['output_format'].downcase.to_sym == t }
27
+ source = params['diagram_source']
28
+ render_diagram(type, accepts, source, {})
29
+ end
30
+
31
+ post '/:type' do
32
+ type = params['type'].to_sym
33
+ r = request
34
+ accepts = lambda { |t| r.accept?(to_mime_type(t)) }
35
+ source = request.body.read
36
+ render_diagram(type, accepts, source, {})
37
+ end
38
+
39
+ post '/:type/:format' do
40
+ type = params['type'].to_sym
41
+ accepts = lambda { |t| params['format'].downcase.to_sym == t }
42
+ source = request.body.read
43
+ render_diagram(type, accepts, source, {})
44
+ end
45
+
46
+ def to_mime_type(type)
47
+ case type
48
+ when :pdf
49
+ 'application/pdf'
50
+ when :png
51
+ 'image/png'
52
+ when :svg
53
+ 'image/svg'
54
+ when :txt
55
+ 'text/plain'
56
+ else
57
+ nil
58
+ end
59
+ end
60
+
61
+ def get_converter(type)
62
+ case type
63
+ when :graphviz
64
+ GraphvizConverter.new
65
+ else
66
+ nil
67
+ end
68
+ end
69
+
70
+ def render_diagram(type, accepts, code, attributes)
71
+ converter = get_converter(type.downcase.to_sym)
72
+ return [500, "Unsupported diagram type #{type}"] unless converter
73
+
74
+ format = converter.supported_formats.find {|f| accepts.call(f)}
75
+ return [500, "Could not determine supported output format"] unless format
76
+
77
+ source = ServerSource.new(code, attributes)
78
+ options = converter.collect_options(source, type.downcase)
79
+ diagram = converter.convert(source, format, options)
80
+
81
+ content_type to_mime_type(format)
82
+ diagram
83
+ end
84
+ end
85
+
86
+ class ServerSource
87
+ include Asciidoctor::Diagram::DiagramSource
88
+
89
+ def initialize(source, attributes)
90
+ @source = source
91
+ @attributes = attributes
92
+ end
93
+
94
+ def attr(name, default_value = nil, inherit = nil)
95
+ @attributes[name] || default_value
96
+ end
97
+
98
+ def base_dir
99
+ nil
100
+ end
101
+
102
+ def code
103
+ @source
104
+ end
105
+
106
+ def config
107
+ {}
108
+ end
109
+
110
+ def find_command(cmd, options = nil)
111
+ Asciidoctor::Diagram::Which.which(cmd, options)
112
+ end
113
+
114
+ def resolve_path(target, start = nil)
115
+ target
116
+ end
117
+
118
+ def image_name
119
+ "image"
120
+ end
121
+
122
+ def should_process?(image_file, image_metadata)
123
+ true
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,54 @@
1
+ require_relative '../diagram_converter'
2
+ require_relative '../util/cli_generator'
3
+ require_relative '../util/platform'
4
+
5
+ module Asciidoctor
6
+ module Diagram
7
+ # @private
8
+ class LilypondConverter
9
+ include DiagramConverter
10
+ include CliGenerator
11
+
12
+
13
+ def supported_formats
14
+ [:png, :pdf]
15
+ end
16
+
17
+ def collect_options(source, name)
18
+ {
19
+ :resolution => source.attr('resolution', nil, name)
20
+ }
21
+ end
22
+
23
+ def convert(source, format, options)
24
+ code = <<-EOF
25
+ \\paper{
26
+ oddFooterMarkup=##f
27
+ oddHeaderMarkup=##f
28
+ bookTitleMarkup=##f
29
+ scoreTitleMarkup=##f
30
+ }
31
+
32
+ EOF
33
+ code << source.to_s
34
+
35
+ resolution = options[:resolution]
36
+
37
+ generate_stdin(source.find_command('lilypond'), format.to_s, code) do |tool_path, output_path|
38
+ args = [tool_path, '-daux-files=#f', '-dbackend=eps', '-dno-gs-load-fonts', '-dinclude-eps-fonts', '-o', Platform.native_path(output_path), '-f', format.to_s]
39
+
40
+ args << '-dsafe'
41
+ args << "-dresolution=#{resolution}" if resolution
42
+ args << "-dpixmap-format=pngalpha" if format == :png
43
+
44
+ args << '-'
45
+
46
+ {
47
+ :args => args,
48
+ :out_file => "#{output_path}.#{format.to_s}"
49
+ }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'converter'
2
+ require_relative '../diagram_processor'
3
+
4
+ module Asciidoctor
5
+ module Diagram
6
+ class LilypondBlockProcessor < DiagramBlockProcessor
7
+ use_converter LilypondConverter
8
+ end
9
+
10
+ class LilypondBlockMacroProcessor < DiagramBlockMacroProcessor
11
+ use_converter LilypondConverter
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ require 'asciidoctor/extensions'
2
+ require_relative 'lilypond/extension'
3
+
4
+ Asciidoctor::Extensions.register do
5
+ block Asciidoctor::Diagram::LilypondBlockProcessor, :lilypond
6
+ block_macro Asciidoctor::Diagram::LilypondBlockMacroProcessor, :lilypond
7
+ end
@@ -0,0 +1,122 @@
1
+ require_relative '../diagram_converter'
2
+ require_relative '../util/cli_generator'
3
+ require 'tempfile'
4
+ require 'open3'
5
+
6
+ module Asciidoctor
7
+ module Diagram
8
+ # @private
9
+ class MemeConverter
10
+ include DiagramConverter
11
+
12
+
13
+ def supported_formats
14
+ [:png, :gif]
15
+ end
16
+
17
+
18
+ def collect_options(source, name)
19
+ bg_img = source.attr('background', nil, name)
20
+ raise "background attribute is required" unless bg_img
21
+
22
+ options = source.attr('options', '', name).split(',')
23
+
24
+ {
25
+ :bg_img => bg_img,
26
+ :top_label => source.attr('top'),
27
+ :bottom_label => source.attr('bottom'),
28
+ :fill_color => source.attr('fillcolor', nil, name) || source.attr('fill-color', nil, name),
29
+ :stroke_color => source.attr('strokecolor', nil, name) || source.attr('stroke-color', nil, name),
30
+ :stroke_width => source.attr('strokewidth', nil, name) || source.attr('stroke-width', nil, name),
31
+ :font => source.attr('font', 'Impact', name),
32
+ :noupcase => options.include?('noupcase'),
33
+ }
34
+ end
35
+
36
+ def convert(source, format, options)
37
+ convert = source.find_command('convert')
38
+ identify = source.find_command('identify')
39
+
40
+ bg_img = options[:bg_img]
41
+ raise "background attribute is required" unless bg_img
42
+
43
+ bg_img = source.resolve_path(bg_img, source.attr('imagesdir'))
44
+
45
+ top_label = options[:top]
46
+ bottom_label = options[:bottom]
47
+ fill_color = options[:fill_color] || 'white'
48
+ stroke_color = options[:stroke_color] || 'black'
49
+ stroke_width = options[:stroke_width] || '2'
50
+ font = options[:font] || 'Impact'
51
+ noupcase = options[:noupcase]
52
+
53
+ dimensions = Cli.run(identify, '-format', '%w %h', bg_img)[:out].match(/(?<w>\d+) (?<h>\d+)/)
54
+ bg_width = dimensions['w'].to_i
55
+ bg_height = dimensions['h'].to_i
56
+ label_width = bg_width
57
+ label_height = bg_height / 5
58
+
59
+ if top_label
60
+ top_img = Tempfile.new(['meme', '.png'])
61
+ Cli.run(
62
+ convert,
63
+ '-background', 'none',
64
+ '-fill', fill_color,
65
+ '-stroke', stroke_color,
66
+ '-strokewidth', stroke_width,
67
+ '-font', font,
68
+ '-size', "#{label_width}x#{label_height}",
69
+ '-gravity', 'north',
70
+ "label:#{prepare_label(top_label, noupcase)}",
71
+ top_img.path
72
+ )
73
+ else
74
+ top_img = nil
75
+ end
76
+
77
+ if bottom_label
78
+ bottom_img = Tempfile.new(['meme', '.png'])
79
+ Cli.run(
80
+ convert,
81
+ '-background', 'none',
82
+ '-fill', fill_color,
83
+ '-stroke', stroke_color,
84
+ '-strokewidth', stroke_width,
85
+ '-font', font,
86
+ '-size', "#{label_width}x#{label_height}",
87
+ '-gravity', 'south',
88
+ "label:#{prepare_label(bottom_label, noupcase)}",
89
+ bottom_img.path
90
+ )
91
+ else
92
+ bottom_img = nil
93
+ end
94
+
95
+ final_img = Tempfile.new(['meme', ".#{format.to_s}"])
96
+
97
+ args = [convert, bg_img]
98
+ if top_img
99
+ args << top_img.path << '-geometry' << '+0+0' << '-composite'
100
+ end
101
+
102
+ if bottom_img
103
+ args << bottom_img.path << '-geometry' << "+0+#{bg_height - label_height}" << '-composite'
104
+ end
105
+
106
+ args << final_img.path
107
+
108
+ Cli.run(*args)
109
+
110
+ File.binread(final_img)
111
+ end
112
+
113
+ private
114
+
115
+ def prepare_label(label, noupcase)
116
+ label = label.upcase unless noupcase
117
+ label = label.gsub(' // ', '\n')
118
+ label
119
+ end
120
+ end
121
+ end
122
+ end