#!/usr/bin/env ruby # -*- coding: utf-8 -*- #========================== # graph.rb #========================== # # Image utility functions to inspect text font metrics # # This file is part of RSyntaxTree, which is a ruby port of Andre Eisenbach's # excellent program phpSyntaxTree. # # Copyright (c) 2007-2018 Yoichiro Hasebe # Copyright (c) 2003-2004 Andre Eisenbach require 'rmagick' include Magick class Graph def initialize(e_list, metrics, symmetrize, color, leafstyle, multibyte, font, font_size) # Set class-specific parameters beforehand in subclass # Store parameters @e_list = e_list @m = metrics @multibyte = multibyte @leafstyle = leafstyle @symmetrize = symmetrize # Calculate image dimensions @e_height = font_size + @m[:e_padd] * 2 h = @e_list.get_level_height w = calc_level_width(0) @width = w + @m[:b_side] * 2 @height = h * @e_height + (h-1) * (@m[:v_space] + font_size) + @m[:b_topbot] * 2 # Initialize the image and colors @col_bg = "none" @col_fg = "black" @col_line = "black" if color @col_node = "blue" @col_leaf = "green" @col_trace = "red" else @col_node = "black" @col_leaf = "black" @col_trace = "black" end @main_height = img_get_txt_height("l", font, font_size) @sub_size = (font_size * SUBSCRIPT_CONST) @sub_space_width = img_get_txt_width("l", font, @sub_size) end def img_get_txt_metrics(text, font, font_size, multiline) background = Image.new(500, 250) gc = Draw.new gc.annotate(background, 0, 0, 0, 0, text) do |gc| gc.font = font gc.pointsize = font_size gc.gravity = CenterGravity gc.stroke = 'none' end if multiline metrics = gc.get_multiline_type_metrics(background, text) else metrics = gc.get_type_metrics(background, text) end return metrics end # Calculate the width of the element. If the element is # a node, the calculation will be performed recursively # for all child elements. def calc_element_width(e) w = 0 children = @e_list.get_children(e.id) if(children.length == 0) w = img_get_txt_width(e.content, @font, @font_size) + @font_size else children.each do |child| child_e = @e_list.get_id(child) w += calc_element_width(child_e) end tw = img_get_txt_width(e.content, @font, @font_size) + @font_size if(tw > w) fix_child_size(e.id, w, tw) w = tw end end @e_list.set_element_width(e.id, w) return w end # Calculate the width of all elements in a certain level def calc_level_width(level) w = 0 e = @e_list.get_first while e if(e.level == level) w += calc_element_width(e) end e = @e_list.get_next end return w end def calc_children_width(id) left = 0 right = 0 c_list = @e_list.get_children(id) return nil if c_list.empty? c_list.each do |c| left = c.indent if indent == 0 or left > c.indent end c_list.each do |c| right = c.indent + e.width if c.indent + c.width > right end return [left, right] end def get_children_indent(id) calc_children_width(id)[0] end def get_children_width(id) calc_children_width(id)[1] - get_children_indent(id) end # Parse the elements in the list top to bottom and # draw the elements into the image. # As we it iterate through the levels, the element # indentation is calculated. def parse_list # Calc element list recursively.... e_arr = @e_list.get_elements h = @e_list.get_level_height h.times do |i| x = 0 e_arr.each do |j| if (j.level == i) cw = @e_list.get_element_width(j.id) parent_indent = @e_list.get_indent(j.parent) if (x < parent_indent) x = parent_indent end @e_list.set_indent(j.id, x) if !@symmetrize draw_element(x, i, cw, j.content, j.type) if(j.parent != 0 ) words = j.content.split(" ") unless @leafstyle == "nothing" && ETYPE_LEAF == j.type if (@leafstyle == "triangle" && ETYPE_LEAF == j.type && x == parent_indent && words.length > 0) txt_width = img_get_txt_width(j.content, @font, @font_size) triangle_to_parent(x, i, cw, txt_width, @symmetrize) elsif (@leafstyle == "auto" && ETYPE_LEAF == j.type && x == parent_indent) if words.length > 1 || j.triangle txt_width = img_get_txt_width(j.content, @font, @font_size) triangle_to_parent(x, i, cw, txt_width, @symmetrize) else line_to_parent(x, i, cw, @e_list.get_indent(j.parent), @e_list.get_element_width(j.parent)) end else line_to_parent(x, i, cw, @e_list.get_indent(j.parent), @e_list.get_element_width(j.parent)) end end end end x += cw end end end return true if !@symmetrize h.times do |i| curlevel = h - i - 1 indent = 0 e_arr.each_with_index do |j, idx| if (j.level == curlevel) # Draw a line to the parent element children = @e_list.get_children(j.id) tw = img_get_txt_width(j.content, @font, @font_size) if children.length > 1 left, right = -1, -1 children.each do |child| k = @e_list.get_id(child) kw = img_get_txt_width(k.content, @font, @font_size) left = k.indent + kw / 2 if k.indent + kw / 2 < left or left == -1 right = k.indent + kw / 2 if k.indent + kw / 2 > right end draw_element(left, curlevel, right - left, j.content, j.type) @e_list.set_indent(j.id, left + (right - left) / 2 - tw / 2) children.each do |child| k = @e_list.get_id(child) words = k.content.split(" ") dw = img_get_txt_width(k.content, @font, @font_size) unless @leafstyle == "nothing" && ETYPE_LEAF == k.type if (@leafstyle == "triangle" && ETYPE_LEAF == k.type && k.indent == j.indent && words.length > 0) txt_width = img_get_txt_width(k.content, @font, @font_size) triangle_to_parent(k.indent, curlevel + 1, dw, txt_width) elsif (@leafstyle == "auto" && ETYPE_LEAF == k.type && k.indent == j.indent) if words.length > 1 || k.triangle txt_width = img_get_txt_width(k.content, @font, @font_size) triangle_to_parent(k.indent, curlevel + 1, dw, txt_width) else line_to_parent(k.indent, curlevel + 1, dw, j.indent, tw) end else line_to_parent(k.indent, curlevel + 1, dw, j.indent, tw) end end end else unless children.empty? k = @e_list.get_id(children[0]) kw = img_get_txt_width(k.content, @font, @font_size) left = k.indent right = k.indent + kw draw_element(left, curlevel, right - left, j.content, j.type) @e_list.set_indent(j.id, left + (right - left) / 2 - tw / 2) else parent = @e_list.get_id(j.parent) pw = img_get_txt_width(parent.content, @font, @font_size) pleft = parent.indent pright = pleft + pw left = j.indent right = left + tw if pw > tw left = pleft right = pright end draw_element(left, curlevel, right - left, j.content, j.type) @e_list.set_indent(j.id, left + (right - left) / 2 - tw / 2) end unless children.empty? k = @e_list.get_id(children[0]) words = k.content.split(" ") dw = img_get_txt_width(k.content, @font, @font_size) unless @leafstyle == "nothing" && ETYPE_LEAF == k.type if (@leafstyle == "triangle" && ETYPE_LEAF == k.type && words.length > 0) txt_width = img_get_txt_width(k.content, @font, @font_size) triangle_to_parent(k.indent, curlevel + 1, dw, txt_width) elsif (@leafstyle == "auto" && ETYPE_LEAF == k.type) if words.length > 1 || k.triangle txt_width = img_get_txt_width(k.content, @font, @font_size) triangle_to_parent(k.indent, curlevel + 1, dw, txt_width) else line_to_parent(k.indent, curlevel + 1, dw, j.indent, tw) end else line_to_parent(k.indent, curlevel + 1, dw, j.indent, tw) end end end end end end end end # Calculate top position from row (level) def row2px(row) @m[:b_topbot] + @e_height * row + (@m[:v_space] + @font_size) * row end def get_txt_only(text) text = text.strip if /\A([\+\-\=\*]+).+/ =~ text prefix = $1 prefix_l = Regexp.escape(prefix) prefix_r = Regexp.escape(prefix.reverse) if /\A#{prefix_l}(.+)#{prefix_r}\z/ =~ text return $1 end end return text end def img_get_txt_height(text, font, font_size, multiline = false) metrics = img_get_txt_metrics(text, font, font_size, multiline) y = metrics.height return y end end