Class: Asciidoctor::Ldl::Renderer

Inherits:
Object
  • Object
show all
Defined in:
lib/asciidoctor/ldl/renderer.rb

Overview

Renders LDL source into an image file by shelling out to the bundled Node helper. Handles output-directory resolution and content-hash caching so an unchanged diagram is only rendered once.

Kept intentionally free of Asciidoctor-specific types so it is trivial to unit test in isolation.

Defined Under Namespace

Classes: Error

Constant Summary collapse

SCRIPT =
File.expand_path('js/ldl_render.mjs', __dir__)
FORMATS =
%w[svg png].freeze
DEFAULT_FONT_FAMILY =

The renderer labels diagram text font-family="sans-serif". Some consumers (notably prawn-svg in asciidoctor-pdf) map that generic family to a glyph-poor built-in font that lacks − (U+2212) and ≥ (U+2265). This default names real, glyph-complete sans-serif fonts first, ending in the generic so browsers/resvg still resolve it. It deliberately omits the PDF built-ins (Helvetica/Arial-AFM) so prawn-svg only uses one of these when it is actually registered in the theme — otherwise it falls through to sans-serif. Set the font-family / ldl-font-family attribute to override, or to an empty value to keep the renderer's "sans-serif".

'DejaVu Sans, Bitstream Vera Sans, Liberation Sans, Arial, sans-serif'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Renderer

opts is a plain Hash with symbol keys:

:format, :scale, :theme, :show_ids, :show_labels, :font_family,
:node, :package_dir, :out_dir, :cache


37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/asciidoctor/ldl/renderer.rb', line 37

def initialize(opts = {})
  @format      = normalize_format(opts[:format])
  @scale       = normalize_scale(opts[:scale])
  @theme       = (opts[:theme] || 'light').to_s.downcase
  @show_ids    = truthy(opts[:show_ids], false)
  @show_labels = truthy(opts[:show_labels], true)
  @font_family = normalize_font_family(opts[:font_family])
  @node        = opts[:node] || 'node'
  @package_dir = opts[:package_dir]
  @out_dir     = opts[:out_dir] || Dir.pwd
  @cache       = opts.key?(:cache) ? opts[:cache] : true
end

Instance Attribute Details

#formatObject (readonly)

Returns the value of attribute format.



50
51
52
# File 'lib/asciidoctor/ldl/renderer.rb', line 50

def format
  @format
end

#out_dirObject (readonly)

Returns the value of attribute out_dir.



50
51
52
# File 'lib/asciidoctor/ldl/renderer.rb', line 50

def out_dir
  @out_dir
end

#scaleObject (readonly)

Returns the value of attribute scale.



50
51
52
# File 'lib/asciidoctor/ldl/renderer.rb', line 50

def scale
  @scale
end

#themeObject (readonly)

Returns the value of attribute theme.



50
51
52
# File 'lib/asciidoctor/ldl/renderer.rb', line 50

def theme
  @theme
end

Instance Method Details

#command_for(out_path) ⇒ Object

Assemble the node command line (exposed for testing).



90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/asciidoctor/ldl/renderer.rb', line 90

def command_for(out_path)
  cmd = [@node, SCRIPT,
         '--format', @format,
         '--scale', sprintf('%g', @scale),
         '--theme', @theme,
         '--out', out_path]
  cmd << (@show_ids ? '--show-ids' : '--no-show-ids')
  cmd << (@show_labels ? '--show-labels' : '--no-show-labels')
  cmd.push('--font-family', @font_family) if @font_family
  cmd.push('--package-dir', @package_dir) if @package_dir
  cmd
end

#render(source, basename = nil) ⇒ Object

Render source and return the basename of the generated file (which lives in out_dir). Pass basename (without extension) to get a stable, human-chosen file name instead of a content hash. Raises Renderer::Error on failure.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/asciidoctor/ldl/renderer.rb', line 56

def render(source, basename = nil)
  FileUtils.mkdir_p(@out_dir)
  filename = target_filename(source, basename)
  path = File.join(@out_dir, filename)
  key = digest(source)

  return filename if @cache && cache_valid?(path, key)

  cmd = command_for(path)
  stdout, stderr, status = Open3.capture3(*cmd, stdin_data: source, binmode: true)

  unless status.success?
    detail = stderr.to_s.strip
    detail = stdout.to_s.strip if detail.empty?
    raise Error, "LDL rendering failed (#{@format}): #{detail}"
  end
  unless File.file?(path)
    raise Error, "LDL renderer produced no output at #{path}: #{stderr.to_s.strip}"
  end
  write_cache_key(path, key) if @cache
  filename
end

#target_filename(source, basename = nil) ⇒ Object

The file name for source under the current options. Uses basename verbatim when given, otherwise a content-addressed name.



81
82
83
84
85
86
87
# File 'lib/asciidoctor/ldl/renderer.rb', line 81

def target_filename(source, basename = nil)
  if basename && !basename.to_s.strip.empty?
    "#{sanitize_basename(basename)}.#{@format}"
  else
    "ldl-#{digest(source)}.#{@format}"
  end
end