module Asciidoctor::Diagram::Extensions::DiagramProcessor

Mixin that provides the basic machinery for image generation. When this module is included it will include the FormatRegistry into the singleton class of the target class.

Constants

DIGIT_CHAR_RANGE
IMAGE_PARAMS

Public Class Methods

included(mod) click to toggle source
# File lib/asciidoctor-diagram/extensions.rb, line 91
def self.included(mod)
  class << mod
    include FormatRegistry
  end
end

Public Instance Methods

process(parent, reader_or_target, attributes) click to toggle source

Processes the diagram block or block macro by converting it into an image or literal block.

@param parent [Asciidoctor::AbstractBlock] the parent asciidoc block of the block or block macro being processed @param reader_or_target [Asciidoctor::Reader, String] a reader that provides the contents of a block or the

target value of a block macro

@param attributes [Hash] the attributes of the block or block macro @return [Asciidoctor::AbstractBlock] a new block that replaces the original block or block macro

# File lib/asciidoctor-diagram/extensions.rb, line 104
def process(parent, reader_or_target, attributes)
  source = create_source(parent, reader_or_target, attributes.dup)

  format = source.attributes.delete('format') || source.attr('format', self.class.default_format, name)
  format = format.to_sym if format.respond_to?(:to_sym)

  raise "Format undefined" unless format

  generator_info = self.class.formats[format]

  raise "#{self.class.name} does not support output format #{format}" unless generator_info

  begin
    title = source.attributes.delete 'title'
    caption = source.attributes.delete 'caption'

    case generator_info[:type]
      when :literal
        block = create_literal_block(parent, source, generator_info)
      else
        block = create_image_block(parent, source, format, generator_info)
    end

    block.title = title
    block.assign_caption(caption, 'figure')
    block
  rescue => e
    case source.attr('on-error', 'log', 'diagram')
      when 'abort'
        raise e
      else
        text = "Failed to generate image: #{e.message}"
        warn_msg = text.dup
        if $VERBOSE
          warn_msg << "\n" << e.backtrace.join("\n")
        end
        warn %(asciidoctor-diagram: ERROR: #{warn_msg})
        text << "\n"
        text << source.code
        Asciidoctor::Block.new parent, :listing, :source => text, :attributes => attributes
    end

  end
end

Protected Instance Methods

create_source(parent_block, reader_or_target, attributes) click to toggle source

Creates a DiagramSource object for the block or block macro being processed. Classes using this mixin must implement this method.

@param parent_block [Asciidoctor::AbstractBlock] the parent asciidoc block of the block or block macro being processed @param reader_or_target [Asciidoctor::Reader, String] a reader that provides the contents of a block or the

target value of a block macro

@param attributes [Hash] the attributes of the block or block macro

@return [DiagramSource] an object that implements the interface described by DiagramSource

@abstract

# File lib/asciidoctor-diagram/extensions.rb, line 162
def create_source(parent_block, reader_or_target, attributes)
  raise NotImplementedError.new
end

Private Instance Methods

cache_dir(parent) click to toggle source
# File lib/asciidoctor-diagram/extensions.rb, line 266
def cache_dir(parent)
  document = parent.document
  cache_dir = '.asciidoctor/diagram'
  base_dir = parent.attr('outdir', nil, true) || doc_option(document, :to_dir)
  parent.normalize_system_path(cache_dir, base_dir)
end
create_image_block(parent, source, format, generator_info) click to toggle source
# File lib/asciidoctor-diagram/extensions.rb, line 169
def create_image_block(parent, source, format, generator_info)
  image_name = "#{source.image_name}.#{format}"
  image_dir = image_output_dir(parent)
  cache_dir = cache_dir(parent)
  image_file = parent.normalize_system_path image_name, image_dir
  metadata_file = parent.normalize_system_path "#{image_name}.cache", cache_dir

  if File.exist? metadata_file
    metadata = File.open(metadata_file, 'r') { |f| JSON.load f }
  else
    metadata = {}
  end

  image_attributes = source.attributes

  if !File.exist?(image_file) || source.should_process?(image_file, metadata)
    params = IMAGE_PARAMS[format]

    result = instance_exec(parent, source, &generator_info[:generator])

    result.force_encoding(params[:encoding])

    metadata = source.create_image_metadata
    metadata['width'], metadata['height'] = params[:decoder].get_image_size(result)

    FileUtils.mkdir_p(File.dirname(image_file)) unless Dir.exist?(File.dirname(image_file))
    File.open(image_file, 'wb') { |f| f.write result }

    FileUtils.mkdir_p(File.dirname(metadata_file)) unless Dir.exist?(File.dirname(metadata_file))
    File.open(metadata_file, 'w') { |f| JSON.dump(metadata, f) }
  end

  image_attributes['target'] = parent.attr('data-uri', nil, true) ? image_file : image_name

  scale = image_attributes['scale']
  if scalematch = /(\d+(?:\.\d+))/.match(scale)
    scale_factor = scalematch[1].to_f
  else
    scale_factor = 1.0
  end

  if /html/i =~ parent.document.attributes['backend']
    image_attributes.delete('scale')
    if metadata['width'] && !image_attributes['width']
      image_attributes['width'] = (metadata['width'] * scale_factor).to_i
    end
    if metadata['height'] && !image_attributes['height']
      image_attributes['height'] = (metadata['height'] * scale_factor).to_i
    end
  end

  image_attributes['alt'] ||= if title_text = image_attributes['title']
                                title_text
                              elsif target = image_attributes['target']
                                (File.basename(target, File.extname(target)) || '').tr '_-', ' '
                              else
                                'Diagram'
                              end

  image_attributes['alt'] = parent.sub_specialchars image_attributes['alt']

  parent.document.register(:images, image_name)
  if (scaledwidth = image_attributes['scaledwidth'])
    # append % to scaledwidth if ends in number (no units present)
    if DIGIT_CHAR_RANGE.include?((scaledwidth[-1] || 0).ord)
      image_attributes['scaledwidth'] = %(#{scaledwidth}%)
    end
  end

  Asciidoctor::Block.new parent, :image, :content_model => :empty, :attributes => image_attributes
end
create_literal_block(parent, source, generator_info) click to toggle source
# File lib/asciidoctor-diagram/extensions.rb, line 273
def create_literal_block(parent, source, generator_info)
  literal_attributes = source.attributes
  literal_attributes.delete('target')

  result = instance_exec(parent, source, &generator_info[:generator])

  result.force_encoding(Encoding::UTF_8)
  Asciidoctor::Block.new parent, :literal, :source => result, :attributes => literal_attributes
end
doc_option(document, key) click to toggle source
# File lib/asciidoctor-diagram/extensions.rb, line 283
def doc_option(document, key)
  if document.respond_to?(:options)
    value = document.options[key]
  else
    value = nil
  end

  if document.nested? && value.nil?
    doc_option(document.parent_document, key)
  else
    value
  end
end
image_output_dir(parent) click to toggle source
# File lib/asciidoctor-diagram/extensions.rb, line 251
def image_output_dir(parent)
  document = parent.document

  images_dir = parent.attr('imagesoutdir', nil, true)

  if images_dir
    base_dir = nil
  else
    base_dir = parent.attr('outdir', nil, true) || doc_option(document, :to_dir)
    images_dir = parent.attr('imagesdir', nil, true)
  end

  parent.normalize_system_path(images_dir, base_dir)
end
scale(size, factor) click to toggle source
# File lib/asciidoctor-diagram/extensions.rb, line 241
def scale(size, factor)
  if match = /(\d+)(.*)/.match(size)
    value = match[1].to_i
    unit = match[2]
    (value * factor).to_i.to_s + unit
  else
    size
  end
end