Class: Graphomaton::Exporters::Png

Inherits:
Object
  • Object
show all
Includes:
Graphomaton::ExporterIntrospection
Defined in:
lib/graphomaton/exporters/png.rb

Defined Under Namespace

Classes: ConversionError

Constant Summary collapse

PNG_SIGNATURE =
"\x89PNG\r\n\x1A\n".b.freeze
DEFAULT_SCALE =
1.0
DEFAULT_CONVERTER =
:auto
DEFAULT_TIMEOUT =
ProcessRunner::DEFAULT_TIMEOUT
DEFAULT_MAX_OUTPUT_BYTES =
ProcessRunner::DEFAULT_MAX_STDOUT_BYTES
CONVERTER_COMMANDS =
{
  rsvg: ['rsvg-convert', '--format', 'png', '-'],
  magick: ['magick', 'svg:-', 'png:-'],
  convert: ['convert', 'svg:-', 'png:-']
}.freeze
CONVERTER_OPTIONS =
([:auto] + CONVERTER_COMMANDS.keys).freeze

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Graphomaton::ExporterIntrospection

#capabilities, #losses_for

Constructor Details

#initialize(automaton) ⇒ Png

Returns a new instance of Png.



38
39
40
# File 'lib/graphomaton/exporters/png.rb', line 38

def initialize(automaton)
  @automaton = automaton
end

Class Method Details

.available?(converter: DEFAULT_CONVERTER) ⇒ Boolean

Returns:

  • (Boolean)


26
27
28
# File 'lib/graphomaton/exporters/png.rb', line 26

def self.available?(converter: DEFAULT_CONVERTER)
  !available_command(converter: converter).nil?
end

.available_command(converter: DEFAULT_CONVERTER) ⇒ Object



30
31
32
33
34
35
36
# File 'lib/graphomaton/exporters/png.rb', line 30

def self.available_command(converter: DEFAULT_CONVERTER)
  resolved_converter = resolve_converter(converter)
  return CONVERTER_COMMANDS[resolved_converter] if resolved_converter != :auto && executable?(CONVERTER_COMMANDS[resolved_converter].first)
  return nil if resolved_converter != :auto

  CONVERTER_COMMANDS.values.find { |command| executable?(command.first) }
end

.executable?(command) ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
# File 'lib/graphomaton/exporters/png.rb', line 98

def self.executable?(command)
  !ProcessRunner.which(command).nil?
end

.resolve_converter(converter) ⇒ Object

Raises:

  • (ArgumentError)


102
103
104
105
106
107
# File 'lib/graphomaton/exporters/png.rb', line 102

def self.resolve_converter(converter)
  resolved = converter.to_sym
  return resolved if CONVERTER_OPTIONS.include?(resolved)

  raise ArgumentError, "Unknown PNG converter: #{converter.inspect}. Available converters: #{CONVERTER_OPTIONS.join(', ')}"
end

Instance Method Details

#export(width = 800, height = 600, theme: Svg::DEFAULT_THEME, scale: DEFAULT_SCALE, converter: DEFAULT_CONVERTER, timeout: DEFAULT_TIMEOUT, max_output_bytes: DEFAULT_MAX_OUTPUT_BYTES, **svg_options) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/graphomaton/exporters/png.rb', line 42

def export(width = 800, height = 600, theme: Svg::DEFAULT_THEME, scale: DEFAULT_SCALE, converter: DEFAULT_CONVERTER,
           timeout: DEFAULT_TIMEOUT, max_output_bytes: DEFAULT_MAX_OUTPUT_BYTES, **svg_options)
  export_result(
    width,
    height,
    theme: theme,
    scale: scale,
    converter: converter,
    timeout: timeout,
    max_output_bytes: max_output_bytes,
    **svg_options
  ).output
end

#export_result(width = 800, height = 600, theme: Svg::DEFAULT_THEME, scale: DEFAULT_SCALE, converter: DEFAULT_CONVERTER, timeout: DEFAULT_TIMEOUT, max_output_bytes: DEFAULT_MAX_OUTPUT_BYTES, **svg_options) ⇒ Object



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/graphomaton/exporters/png.rb', line 56

def export_result(width = 800, height = 600, theme: Svg::DEFAULT_THEME, scale: DEFAULT_SCALE, converter: DEFAULT_CONVERTER,
                  timeout: DEFAULT_TIMEOUT, max_output_bytes: DEFAULT_MAX_OUTPUT_BYTES, **svg_options)
  command = available_command(converter: converter)
  raise ConversionError, missing_converter_message(converter) unless command

  resolved_scale = resolve_scale(scale)
  svg_result = Svg.new(@automaton).export_result(width, height, theme: theme, **svg_options)
  svg = scale_svg_dimensions(svg_result.output, resolved_scale)
  png, error, status = ProcessRunner.capture3(
    *command,
    stdin_data: svg,
    binmode: true,
    timeout: timeout,
    max_stdout_bytes: max_output_bytes
  )
  png = png.b

  if status.success? && png.start_with?(PNG_SIGNATURE)
    return RenderResult.new(
      output: png.freeze,
      diagnostics: svg_result.diagnostics,
      bounds: svg_result.bounds,
      layout: svg_result.layout
    )
  end
  raise ConversionError, invalid_png_message(command, error) if status.success?

  raise ConversionError, failed_conversion_message(command, error)
rescue ProcessRunner::Error => e
  raise ConversionError, failed_conversion_message(command, e.message)
end