module Asciidoctor::Diagram::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
- TEXT_FORMATS
Public Class Methods
# File lib/asciidoctor-diagram/diagram_processor.rb, line 29 def self.included(host_class) host_class.use_dsl host_class.extend(ClassMethods) end
Public Instance Methods
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/diagram_processor.rb, line 63 def process(parent, reader_or_target, attributes) location = parent.document.reader.cursor_at_mark normalised_attributes = attributes.inject({}) { |h, (k, v)| h[normalise_attribute_name(k)] = v; h } converter = config[:converter].new supported_formats = supported_formats(converter) source = create_source(parent, reader_or_target, normalised_attributes) # memorize current code here for error message to avoid calling wrapped source's code method later code = source.code begin source = converter.wrap_source(source) format = source.attributes.delete('format') || source.global_attr('format', supported_formats[0]) format = format.to_sym if format.respond_to?(:to_sym) raise "Format undefined" unless format raise "#{self.class.name} does not support output format #{format}" unless supported_formats.include?(format) title = source.attributes.delete 'title' caption = source.attributes.delete 'caption' case format when *TEXT_FORMATS block = create_literal_block(parent, source, format, converter) else block = create_image_block(parent, source, format, converter) end block.title = title block.assign_caption(caption, 'figure') block rescue => e case source.global_attr('on-error', 'log') 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 logger.error message_with_context warn_msg, source_location: location text << "\n" text << code Asciidoctor::Block.new parent, :listing, :source => text, :attributes => attributes end end end
Protected Instance Methods
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/diagram_processor.rb, line 133 def create_source(parent_block, reader_or_target, attributes) raise NotImplementedError.new end
Private Instance Methods
Returns the cache directory as an absolute path
# File lib/asciidoctor-diagram/diagram_processor.rb, line 301 def cache_dir(source, parent) cache_dir = source.global_attr('cachedir') if cache_dir resolve_path(parent, cache_dir) else output_dir = output_dir(parent) resolve_path(parent, '.asciidoctor/diagram', output_dir) end end
# File lib/asciidoctor-diagram/diagram_processor.rb, line 156 def create_image_block(parent, source, format, converter) image_name = "#{source.image_name}.#{format}" image_file = parent.normalize_system_path(image_name, image_output_dir(parent)) metadata_file = parent.normalize_system_path("#{image_name}.cache", cache_dir(source, parent)) if File.exist? metadata_file metadata = File.open(metadata_file, 'r') {|f| JSON.load(f, nil, :symbolize_names => true, :create_additions => false) } else metadata = {} end image_attributes = source.attributes options = converter.collect_options(source) if !File.exist?(image_file) || source.should_process?(image_file, metadata) || options != metadata[:options] params = IMAGE_PARAMS[format] server_url = source.global_attr('server-url') if server_url server_type = source.global_attr('server-type') converter = HttpConverter.new(server_url, server_type.to_sym, converter) end options = converter.collect_options(source) result = converter.convert(source, format, options) result.force_encoding(params[:encoding]) metadata = source.create_image_metadata metadata[:options] = options allow_image_optimisation = source.attr('optimise', 'true') == 'true' result, metadata[:width], metadata[:height] = params[:decoder].post_process_image(result, allow_image_optimisation) 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 scale = image_attributes['scale'] if !converter.native_scaling? && scalematch = /([0-9]+(?:\.[0-9]+)?)/.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 parent.document.register(:images, image_name) node = Asciidoctor::Block.new parent, :image, :content_model => :empty, :attributes => image_attributes alt_text = node.attr('alt') alt_text ||= if title_text = image_attributes['title'] title_text elsif target = image_attributes['target'] (File.basename(target, File.extname(target)) || '').tr '_-', ' ' else 'Diagram' end alt_text = parent.sub_specialchars(alt_text) node.set_attr('alt', alt_text) if (scaledwidth = node.attr('scaledwidth')) # append % to scaledwidth if ends in number (no units present) if DIGIT_CHAR_RANGE.include?((scaledwidth[-1] || 0).ord) node.set_attr('scaledwidth', %(#{scaledwidth}%)) end end use_absolute_path = source.attr('data-uri', nil, true) if format == :svg if node.option? 'inline' svg_type = 'inline' elsif node.option? 'interactive' svg_type = 'interactive' else svg_type = source.global_attr('svg-type') end case svg_type when nil, 'static' # Nothing to do when 'inline', 'interactive' node.set_option(svg_type) use_absolute_path = true else raise "Unsupported SVG type: #{svg_type}" end end if use_absolute_path node.set_attr('target', image_file) else node.set_attr('target', image_name) if source.global_attr('autoimagesdir') image_path = Pathname.new(image_file) output_path = Pathname.new(parent.normalize_system_path(output_dir(parent))) imagesdir = image_path.relative_path_from(output_path).dirname.to_s node.set_attr('imagesdir', imagesdir) end end node end
# File lib/asciidoctor-diagram/diagram_processor.rb, line 326 def create_literal_block(parent, source, format, converter) literal_attributes = source.attributes literal_attributes.delete('target') options = converter.collect_options(source) result = converter.convert(source, format, options) result.force_encoding(Encoding::UTF_8) Asciidoctor::Block.new parent, :literal, :source => result, :attributes => literal_attributes end
# File lib/asciidoctor-diagram/diagram_processor.rb, line 337 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
Returns the image output directory as an absolute path
# File lib/asciidoctor-diagram/diagram_processor.rb, line 288 def image_output_dir(parent) images_out_dir = parent.attr('imagesoutdir', nil, true) if images_out_dir resolve_path(parent, images_out_dir) else images_dir = parent.attr('imagesdir', nil, true) output_dir = output_dir(parent) resolve_path(parent, images_dir, output_dir) end end
# File lib/asciidoctor-diagram/diagram_processor.rb, line 143 def normalise_attribute_name(k) case k when String k.downcase when Symbol k.to_s.downcase.to_sym else k end end
Returns the general output directory for Asciidoctor
as an absolute path
# File lib/asciidoctor-diagram/diagram_processor.rb, line 312 def output_dir(parent) resolve_path(parent, parent.attr('outdir', nil, true) || doc_option(parent.document, :to_dir)) end
# File lib/asciidoctor-diagram/diagram_processor.rb, line 316 def resolve_path(parent, path, base_dir = nil) if path.nil? # Resolve the base dir itself parent.document.path_resolver.system_path(base_dir) else # Resolve the path with respect to the base dir parent.document.path_resolver.system_path(path, base_dir) end end
# File lib/asciidoctor-diagram/diagram_processor.rb, line 277 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
# File lib/asciidoctor-diagram/diagram_processor.rb, line 139 def supported_formats(converter) converter.supported_formats end