lib/rsyntaxtree/svg_graph.rb in rsyntaxtree-0.9.3 vs lib/rsyntaxtree/svg_graph.rb in rsyntaxtree-1.0.1
- old
+ new
@@ -4,360 +4,506 @@
#==========================
# svg_graph.rb
#==========================
#
# Parses an element list into an SVG tree.
-#
-# This file is part of RSyntaxTree, which is a ruby port of Andre Eisenbach's
-# excellent program phpSyntaxTree.
-#
# Copyright (c) 2007-2021 Yoichiro Hasebe <yohasebe@gmail.com>
-# Copyright (c) 2003-2004 Andre Eisenbach <andre@ironcreek.net>
require "tempfile"
-require 'graph'
+require 'base_graph'
+require 'utils'
-class SVGGraph < Graph
+module RSyntaxTree
+ class SVGGraph < BaseGraph
+ attr_accessor :width, :height
- def initialize(e_list, metrics, symmetrize, color, leafstyle, multibyte, fontstyle, font, font_cjk, font_size, margin, transparent)
-
- # Store class-specific parameters
- @font = multibyte ? font_cjk : font
- @font_size = font_size
- @transparent = transparent
-
- case fontstyle
- when /(?:sans|cjk)/
- @fontstyle = "\"'Noto Sans JP', 'Noto Sans', sans-serif\""
- @fontcss = "http://fonts.googleapis.com/earlyaccess/notosansjp.css"
- when /(?:serif)/
- @fontstyle = "\"'Noto Serif JP', 'Noto Serif', serif\""
- @fontcss = "https://fonts.googleapis.com/css?family=Noto+Serif+JP"
- when /(?:math)/
- @fontstyle = "\"Latin Modern Roman', sans-serif\""
- @fontcss = "https://cdn.jsdelivr.net/gh/sugina-dev/latin-modern-web@1.0.1/style/latinmodern-roman.css"
+ def initialize(element_list, params)
+ @height = 0
+ @width = 0
+ @extra_lines = []
+ @fontset = params[:fontset]
+ @fontsize = params[:fontsize]
+ @transparent = params[:transparent]
+ @color = params[:color]
+ @fontstyle = params[:fontstyle]
+ @margin = params[:margin].to_i
+ @line_styles = "<line style='stroke:black; stroke-width:#{FONT_SCALING};' x1='X1' y1='Y1' x2='X2' y2='Y2' />\n"
+ @polygon_styles = "<polygon style='fill: none; stroke: black; stroke-width:#{FONT_SCALING};' points='X1 Y1 X2 Y2 X3 Y3' />\n"
+ @text_styles = "<text white-space='pre' alignment-baseline='text-top' style='fill: COLOR; font-size: fontsize' x='X_VALUE' y='Y_VALUE'>CONTENT</text>\n"
+ @tree_data = String.new
+ @visited_x = {}
+ @visited_y = {}
+ super(element_list, params)
end
- @margin = margin.to_i
+ def svg_data
+ metrics = parse_list
+ @height = metrics[:height] + @margin * 2
+ @width = metrics[:width] + @margin * 2
- super(e_list, metrics, symmetrize, color, leafstyle, multibyte, @fontstyle, @font_size)
+ x1 = 0 - @margin
+ y1 = 0 - @margin
+ x2 = @width + @margin
+ y2 = @height + @margin
+ extra_lines = @extra_lines.join("\n")
- @line_styles = "<line style='stroke:black; stroke-width:#{FONT_SCALING};' x1='X1' y1='Y1' x2='X2' y2='Y2' />\n"
- @polygon_styles = "<polygon style='fill: none; stroke: black; stroke-width:#{FONT_SCALING};' points='X1 Y1 X2 Y2 X3 Y3' />\n"
- @text_styles = "<text letter-spacing='0' word-spacing='0' kerning='0' style='fill: COLOR; font-size: FONT_SIZE ST WA' x='X_VALUE' y='Y_VALUE' TD font-family=#{@fontstyle}>CONTENT</text>\n"
- @tree_data = String.new
- end
+ as2 = $h_gap_between_nodes / 2 * 0.8
+ as = as2 / 2
- def get_left_most(tree_data)
- xs = @tree_data.scan(/x1?=['"]([^'"]+)['"]/).map{|m| m.first.to_i}
- xs.min
- end
-
- def svg_data
- parse_list
- lm = get_left_most(@tree_data)
- width = @width - lm + @margin * 2
- height = @height + @margin * 2
-
- header =<<EOD
+ header =<<EOD
<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
- "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg width="#{width}" height="#{height}" viewBox="#{-@margin + lm}, -#{@margin}, #{@width - lm + @margin * 2}, #{@height + @margin * 2}" version="1.1" xmlns="http://www.w3.org/2000/svg">
-<defs>
-<style>
-@import url(#{@fontcss});
-</style>
-</defs>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+ <svg width="#{@width}" height="#{@height}" viewBox="#{x1}, #{y1}, #{x2}, #{y2}" version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <marker id="arrow" markerUnits="strokeWidth" markerWidth="#{as2}" markerHeight="#{as2}" viewBox="0 0 #{as2} #{as2}" refX="#{as}" refY="0">
+ <polyline fill="none" stroke="#{@col_path}" stroke-width="1" points="0,#{as2},#{as},0,#{as2},#{as2}" />
+ </marker>
+ <pattern id="hatchBlack" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
+ <line x1="0" y="0" x2="0" y2="10" stroke="black" stroke-width="4"></line>
+ </pattern>
+ <pattern id="hatchForNode" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
+ <line x1="0" y="0" x2="0" y2="10" stroke="#{@col_node}" stroke-width="4"></line>
+ </pattern>
+ <pattern id="hatchForLeaf" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
+ <line x1="0" y="0" x2="0" y2="10" stroke="#{@col_leaf}" stroke-width="4"></line>
+ </pattern>
+ </defs>
EOD
-
- rect =<<EOD
-<rect x="#{-@margin + lm}" y="-#{@margin}" width="#{@width - lm + @margin * 2}" height="#{@height + @margin * 2}" stroke="none" fill="white" />"
+ rect =<<EOD
+<rect x="#{x1}" y="#{y1}" width="#{x2}" height="#{y2}" stroke="none" fill="white" />"
EOD
- footer = "</svg>"
+ footer = "</svg>"
- if @transparent
- header + @tree_data + footer
- else
- header + rect + @tree_data + footer
+ if @transparent
+ header + @tree_data + extra_lines + footer
+ else
+ header + rect + @tree_data + extra_lines + footer
+ end
end
- end
- # Create a temporary file and returns only its filename
- def create_tempf(basename, ext, num = 10)
- flags = File::RDWR | File::CREAT | File::EXCL
- tfname = ""
- num.times do |i|
- begin
- tfname = "#{basename}.#{$$}.#{i}.#{ext}"
- tfile = File.open(tfname, flags, 0600)
- rescue Errno::EEXIST
- next
+ def draw_a_path(s_x, s_y, t_x, t_y, target_arrow = :none)
+
+ x_spacing = $h_gap_between_nodes * 1.25
+ y_spacing = $height_connector * 0.65
+
+ ymax = [s_y, t_y].max
+ if ymax < @height
+ new_y = @height + y_spacing
+ else
+ new_y = ymax + y_spacing
end
- tfile.close
- return tfname
- end
- end
- :private
- # Add the element into the tree (draw it)
- def draw_element(x, y, w, string, type)
- string = string.sub(/\^\z/){""}
- # Calculate element dimensions and position
- if (type == ETYPE_LEAF) and @leafstyle == "nothing"
- top = row2px(y - 1) + (@font_size * 1.5)
- else
- top = row2px(y)
- end
- left = x + @m[:b_side]
- bottom = top + @e_height
- right = left + w
+ if @visited_x[s_x]
+ new_s_x = s_x - x_spacing * @visited_x[s_x]
+ @visited_x[s_x] += 1
+ else
+ new_s_x = s_x
+ @visited_x[s_x] = 1
+ end
- # Split the string into the main part and the
- # subscript part of the element (if any)
- parts = string.split("_", 2)
- if(parts.length > 1 )
- main = parts[0].strip
- sub = parts[1].gsub(/_/, " ").strip
- else
- main = parts[0].strip
- sub = ""
- end
+ if @visited_x[t_x]
+ new_t_x = t_x - x_spacing * @visited_x[t_x]
+ @visited_x[t_x] += 1
+ else
+ new_t_x = t_x
+ @visited_x[t_x] = 1
+ end
- if /\A\=(.+)\=\z/ =~ main
- main = $1
- main_decoration= "overline"
- elsif /\A\-(.+)\-\z/ =~ main
- main = $1
- main_decoration= "underline"
- elsif /\A\~(.+)\~\z/ =~ main
- main = $1
- main_decoration= "line-through"
- else
- main_decoration= ""
- end
+ s_y += $h_gap_between_nodes / 2
+ t_y += $h_gap_between_nodes / 2
+ new_y += $h_gap_between_nodes / 2
- if /\A\*\*\*(.+)\*\*\*\z/ =~ main
- main = $1
- main_style = "font-style: italic"
- main_weight = "font-weight: bold"
- elsif /\A\*\*(.+)\*\*\z/ =~ main
- main = $1
- main_style = ""
- main_weight = "font-weight: bold"
- elsif /\A\*(.+)\*\z/ =~ main
- main = $1
- main_style = "font-style: italic"
- main_weight = ""
- else
- main_style = ""
- main_weight = ""
- end
+ dashed = true if target_arrow == :none
- if /\A#(.+)#\z/ =~ main
- main = $1
+ if target_arrow == :single
+ @extra_lines << generate_line(new_s_x, s_y, new_s_x, new_y, @col_path, dashed)
+ @extra_lines << generate_line(new_s_x, new_y, new_t_x, new_y, @col_path, dashed)
+ @extra_lines << generate_line(new_t_x, new_y, new_t_x, t_y, @col_path ,dashed, true)
+ elsif target_arrow == :double
+ @extra_lines << generate_line(new_s_x, new_y, new_s_x, s_y, @col_path, dashed, true)
+ @extra_lines << generate_line(new_s_x, new_y, new_t_x, new_y, @col_path, dashed)
+ @extra_lines << generate_line(new_t_x, new_y, new_t_x, t_y, @col_path ,dashed, true)
+ else
+ @extra_lines << generate_line(new_s_x, s_y, new_s_x, new_y, @col_path, dashed)
+ @extra_lines << generate_line(new_s_x, new_y, new_t_x, new_y, @col_path, dashed)
+ @extra_lines << generate_line(new_t_x, new_y, new_t_x, t_y, @col_path ,dashed)
+ end
+
+ @height = new_y if new_y > @height
end
- # Calculate text size for the main and the
- # subscript part of the element
- # symbols for underline/overline removed temporarily
+ def draw_element(element)
+ top = element.vertical_indent
- main_width = 0
- main_height = 0
- main.split(/\\n/).each do |l|
- l_width = img_get_txt_width(l, @font, @font_size)
- main_width = l_width if main_width < l_width
- main_height += img_get_txt_height(l, @font, @font_size)
- end
+ left = element.horizontal_indent
+ bottom = top +$single_line_height
+ right = left + element.content_width
+ txt_pos = left + (right - left) / 2
- if sub != ""
- if /\A\=(.+)\=\z/ =~ sub
- sub = $1
- sub_decoration= "overline"
- elsif /\A\-(.+)\-\z/ =~ sub
- sub = $1
- sub_decoration= "underline"
- elsif /\A\~(.+)\~\z/ =~ sub
- sub = $1
- sub_decoration= "line-through"
+ if(element.type == ETYPE_LEAF)
+ col = @col_leaf
else
- sub_decoration= ""
+ col = @col_node
end
- if /\A\*\*\*(.+)\*\*\*\z/ =~ sub
- sub = $1
- sub_style = "font-style: italic"
- sub_weight = "font-weight: bold"
- elsif /\A\*\*(.+)\*\*\z/ =~ sub
- sub = $1
- sub_style = ""
- sub_weight = "font-weight: bold"
- elsif /\A\*(.+)\*\z/ =~ sub
- sub = $1
- sub_style = "font-style: italic"
- sub_weight = ""
- else
- sub_style = ""
- sub_weight = ""
- end
- sub_height = img_get_txt_height(sub, @font, @font_size)
- sub_width = img_get_txt_width(sub.to_s, @font, @sub_size)
- else
- sub_width = 0
- sub_height = 0
- end
+ text_data = @text_styles.sub(/COLOR/, col)
+ text_data = text_data.sub(/fontsize/, @fontsize.to_s + "px;")
+ text_x = txt_pos - element.content_width / 2
+ text_y = top + $single_line_height - $height_connector_to_text
+ text_data = text_data.sub(/X_VALUE/, text_x.to_s)
+ text_data = text_data.sub(/Y_VALUE/, text_y.to_s)
+ new_text = ""
+ this_x = 0
+ this_y = 0
+ bc = {:x => text_x - $h_gap_between_nodes / 2 , :y => top, :width => element.content_width + $h_gap_between_nodes, :height => nil}
+ element.content.each_with_index do |l, idx|
+ case l[:type]
+ when :border, :bborder
+ x1 = text_x
+ if idx == 0
+ text_y -= l[:height]
+ elsif
+ text_y += l[:height]
+ end
+ y1 = text_y - $single_line_height / 8
+ x2 = text_x + element.content_width
+ y2 = y1
+ this_width = x2 - x1
+ case l[:type]
+ when :border
+ stroke_width = FONT_SCALING
+ when :bborder
+ stroke_width = FONT_SCALING * 2
+ end
+ @extra_lines << "<line style=\"stroke:#{col}; stroke-width:#{stroke_width}; \" x1=\"#{x1}\" y1=\"#{y1}\" x2=\"#{x2}\" y2=\"#{y2}\"></line>"
+ else
+ if element.enclosure == :brackets
+ this_x = txt_pos - element.content_width / 2
+ else
+ ewidth = 0
+ l[:elements].each do |e|
+ ewidth += e[:width]
+ end
+ this_x = txt_pos - (ewidth / 2)
+ end
+ text_y += l[:elements].map{|e| e[:height]}.max if idx != 0
- if /\A#(.+)#\z/ =~ sub
- sub = $1
- end
+ l[:elements].each_with_index do |e, idx|
+ escaped_text = e[:text].gsub('>', '>').gsub('<', '<');
+ decorations = []
+ if e[:decoration].include?(:overline)
+ decorations << "overline"
+ end
- # Center text in the element
- txt_pos = left + (right - left) / 2
+ if e[:decoration].include?(:underline)
+ decorations << "underline"
+ end
- # Select apropriate color
- if(type == ETYPE_LEAF)
- col = @col_leaf
- else
- col = @col_node
- end
+ if e[:decoration].include?(:linethrough)
+ decorations << "line-through"
+ end
+ decoration ="text-decoration=\"" + decorations.join(" ") + "\""
- if(main[0].chr == "<" && main[-1].chr == ">")
- col = @col_trace
- end
+ style = "style=\""
+ if e[:decoration].include?(:small)
+ style += "font-size: #{(SUBSCRIPT_CONST.to_f * 100).to_i}%; "
+ this_y = text_y - (($single_X_metrics.height - $single_X_metrics.height * SUBSCRIPT_CONST) / 4) + 2
+ elsif e[:decoration].include?(:superscript)
+ style += "font-size: #{(SUBSCRIPT_CONST.to_f * 100).to_i}%; "
+ this_y = text_y - ($single_X_metrics.height / 4) + 1
+ elsif e[:decoration].include?(:subscript)
+ style += "font-size: #{(SUBSCRIPT_CONST.to_f * 100).to_i}%; "
+ this_y = text_y + 4
+ else
+ this_y = text_y
+ end
- # Draw main text
- main_data = @text_styles.sub(/COLOR/, col)
- main_data = main_data.sub(/FONT_SIZE/, @font_size.to_s + "px;")
- main_x = txt_pos - (main_width + sub_width) / 2
- main_y = top + @e_height - @m[:e_padd]
- main_data = main_data.sub(/X_VALUE/, main_x.to_s)
- main_data = main_data.sub(/Y_VALUE/, main_y.to_s)
- if /\\n/ =~ main
- lines = main.split(/\\n/)
- new_main = ""
- dy = 0
- lines.each_with_index do |l, idx|
- if idx == 0
- dy = 0
- else
- dy = 1
- main_y += img_get_txt_height(l, @font, @font_size)
+ if e[:decoration].include?(:bold) || e[:decoration].include?(:bolditalic)
+ style += "font-weight: bold; "
+ end
+
+ if e[:decoration].include?(:italic) || e[:decoration].include?(:bolditalic)
+ style += "font-style: italic; "
+ end
+
+ style += "\""
+
+ case @fontstyle
+ when /(?:cjk)/
+ fontstyle = "'WenQuanYi Zen Hei', 'Noto Sans', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', sans-serif"
+ when /(?:sans)/
+ if e[:cjk]
+ fontstyle = "'Noto Sans JP', 'Noto Sans', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', sans-serif"
+ else
+ fontstyle = "'Noto Sans', 'Noto Sans JP', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', sans-serif"
+ end
+ when /(?:serif)/
+ if e[:cjk]
+ fontstyle = "'Noto Serif JP', 'Noto Serif', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', serif"
+ else
+ fontstyle = "'Noto Serif', 'Noto Serif JP', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', serif"
+ end
+ end
+
+ if e[:decoration].include?(:box) || e[:decoration].include?(:circle) || e[:decoration].include?(:bar)
+ enc_height = e[:height]
+ enc_y = this_y - e[:height] * 0.8 + FONT_SCALING
+
+ if e[:text].size == 1
+ enc_width = e[:width]
+ enc_x = this_x
+ else
+ enc_width = e[:width]
+ enc_x = this_x
+ end
+
+ if e[:decoration].include?(:hatched)
+ case element.type
+ when ETYPE_LEAF
+ if @color
+ fill = "url(#hatchForLeaf)"
+ else
+ fill = "url(#hatchBlack)"
+ end
+ when ETYPE_NODE
+ if @color
+ fill = "url(#hatchForNode)"
+ else
+ fill = "url(#hatchBlack)"
+ end
+ end
+ else
+ fill = "none"
+ end
+
+ enc = nil
+ bar = nil
+
+ if e[:decoration].include?(:bstroke)
+ stroke_width = FONT_SCALING * 2.5
+ else
+ stroke_width = FONT_SCALING
+ end
+
+ if e[:decoration].include?(:box)
+ enc = "<rect style='stroke: #{col}; stroke-width:#{stroke_width};'
+ x='#{enc_x}' y='#{enc_y}'
+ width='#{enc_width}' height='#{enc_height}'
+ fill='#{fill}' />\n"
+ elsif e[:decoration].include?(:circle)
+ enc = "<rect style='stroke: #{col}; stroke-width:#{stroke_width};'
+ x='#{enc_x}' y='#{enc_y}' rx='#{enc_height / 2}' ry='#{enc_height / 2}'
+ width='#{enc_width}' height='#{enc_height}'
+ fill='#{fill}' />\n"
+ elsif e[:decoration].include?(:bar)
+ x1 = enc_x
+ y1 = enc_y + enc_height / 2
+ x2 = enc_x + enc_width
+ y2 = y1
+ ar_hwidth = e[:width] / 4.0
+ bar = "<line style='stroke:#{col}; stroke-width:#{stroke_width};' x1='#{x1}' y1='#{y1}' x2='#{x2}' y2='#{y2}'></line>\n"
+ @extra_lines << bar
+
+ if e[:decoration].include?(:arrow_to_l)
+ l_arrowhead = "<polyline stroke-linejoin='bevel' fill='none' stroke='#{col}' stroke-width='#{stroke_width}' points='#{x1 + ar_hwidth},#{y1 + ar_hwidth / 2} #{x1},#{y1} #{x1 + ar_hwidth},#{y1 - ar_hwidth / 2}' />\n"
+ @extra_lines << l_arrowhead
+ end
+
+ if e[:decoration].include?(:arrow_to_r)
+ r_arrowhead = "<polyline stroke-linejoin='bevel' fill='none' stroke='#{col}' stroke-width='#{stroke_width}' points='#{x2 - ar_hwidth},#{y2 - ar_hwidth / 2} #{x2},#{y2} #{x2 - ar_hwidth},#{y2 + ar_hwidth / 2}' />\n"
+ @extra_lines << r_arrowhead
+ end
+
+
+ end
+
+ @extra_lines << enc if enc
+
+ if e[:text].size == 1
+ this_x += (e[:height] - e[:content_width]) / 2
+ else
+ this_x += $width_half_X / 2
+ end
+ new_text << set_tspan(this_x, this_y, style, decoration, fontstyle, escaped_text)
+ if e[:text].size == 1
+ this_x += e[:content_width]
+ this_x += (e[:height] - e[:content_width]) / 2
+ else
+ this_x += e[:content_width]
+ this_x += $width_half_X / 2
+ end
+
+ elsif e[:decoration].include?(:whitespace)
+ this_x += e[:width]
+ next
+ else
+ new_text << set_tspan(this_x, this_y, style, decoration, fontstyle, escaped_text)
+ this_x += e[:width]
+ end
+
+ end
end
- this_width = img_get_txt_width(l, @font, @font_size)
- this_x = txt_pos - (this_width + sub_width) / 2
- new_main << "<tspan x='#{this_x}' y='#{main_y}'>#{l}</tspan>"
- @height = main_y if main_y > @height
+ @height = text_y if text_y > @height
end
- main = new_main
+ bc[:y] = bc[:y] + $height_connector_to_text * 3 / 4
+ bc[:height] = text_y - bc[:y] + $height_connector_to_text
+ if element.enclosure == :brackets
+ @extra_lines << generate_line(bc[:x], bc[:y], bc[:x] + $h_gap_between_nodes / 2, bc[:y], col)
+ @extra_lines << generate_line(bc[:x], bc[:y], bc[:x], bc[:y] + bc[:height], col)
+ @extra_lines << generate_line(bc[:x], bc[:y] + bc[:height], bc[:x] + $h_gap_between_nodes / 2, bc[:y] + bc[:height], col)
+ @extra_lines << generate_line(bc[:x] + bc[:width], bc[:y], bc[:x] + bc[:width] - $h_gap_between_nodes / 2, bc[:y], col)
+ @extra_lines << generate_line(bc[:x] + bc[:width], bc[:y], bc[:x] + bc[:width], bc[:y] + bc[:height], col)
+ @extra_lines << generate_line(bc[:x] + bc[:width], bc[:y] + bc[:height], bc[:x] + bc[:width] - $h_gap_between_nodes / 2, bc[:y] + bc[:height], col)
+ elsif element.enclosure == :rectangle
+ @extra_lines << generate_line(bc[:x], bc[:y], bc[:x] + bc[:width], bc[:y], col)
+ @extra_lines << generate_line(bc[:x], bc[:y], bc[:x], bc[:y] + bc[:height], col)
+ @extra_lines << generate_line(bc[:x] + bc[:width], bc[:y], bc[:x] + bc[:width], bc[:y] + bc[:height], col)
+ @extra_lines << generate_line(bc[:x], bc[:y] + bc[:height], bc[:x] + bc[:width], bc[:y] + bc[:height], col)
+ elsif element.enclosure == :brectangle
+ @extra_lines << generate_line(bc[:x], bc[:y], bc[:x] + bc[:width], bc[:y], col, false, false, 2)
+ @extra_lines << generate_line(bc[:x], bc[:y], bc[:x], bc[:y] + bc[:height], col, false, false, 2)
+ @extra_lines << generate_line(bc[:x] + bc[:width], bc[:y], bc[:x] + bc[:width], bc[:y] + bc[:height], col, false, false, 2)
+ @extra_lines << generate_line(bc[:x], bc[:y] + bc[:height], bc[:x] + bc[:width], bc[:y] + bc[:height], col, false, false, 2)
+ end
+
+ element.content_height = bc[:height]
+ @tree_data += text_data.sub(/CONTENT/, new_text)
end
- @tree_data += main_data.sub(/TD/, "text-decoration='#{main_decoration}'")
- .sub(/ST/, main_style + ";")
- .sub(/WA/, main_weight + ";")
- .sub(/CONTENT/, main)
- # Draw subscript text
- if sub && sub != ""
- sub_data = @text_styles.sub(/COLOR/, col)
- sub_data = sub_data.sub(/FONT_SIZE/, @sub_size.to_s)
- sub_x = txt_pos + (main_width / 2) - (sub_width / 2)
- sub_y = main_y + sub_height / 6
- sub_data = sub_data.sub(/X_VALUE/, sub_x.to_s)
- sub_data = sub_data.sub(/Y_VALUE/, sub_y.to_s)
- @tree_data += sub_data.sub(/TD/, "text-decoration='#{sub_decoration}'")
- .sub(/ST/, sub_style)
- .sub(/WA/, sub_weight)
- .sub(/CONTENT/, sub)
- @height += sub_height / 4
+ def set_tspan(this_x, this_y, style, decoration, fontstyle, text)
+ text.gsub!(/■+/) do |x|
+ num_spaces = x.size
+ "<tspan style='fill:none;'>" + "■" * num_spaces + "</tspan>"
+ end
+ "<tspan x='#{this_x}' y='#{this_y}' #{style} #{decoration} font-family=\"#{fontstyle}\">#{text}</tspan>\n"
end
- end
- # Draw a line between child/parent elements
- def line_to_parent(fromX, fromY, fromW, toX, toW)
- if (fromY == 0 )
- return
- end
+ def draw_paths
+ rockbottom = 0
+ path_pool_target = {}
+ path_pool_other = {}
+ path_pool_source = {}
+ path_flags = []
+ elist = @element_list.get_elements.reverse
- fromTop = row2px(fromY)
- fromLeft = (fromX + fromW / 2 + @m[:b_side])
- toBot = (row2px(fromY - 1 ) + @e_height)
- toLeft = (toX + toW / 2 + @m[:b_side])
+ elist.each do |element|
+ x1 = element.horizontal_indent + element.content_width / 2
+ y1 = element.vertical_indent + element.content_height
+ y1 += $height_connector_to_text if element.enclosure != :none
+ et = element.path
+ et.each do |tr|
+ if /\A>(\d+)\z/ =~ tr
+ tr = $1
+ if path_pool_target[tr]
+ path_pool_target[tr] << [x1, y1]
+ else
+ path_pool_target[tr] = [[x1, y1]]
+ end
+ elsif path_pool_source[tr]
+ if path_pool_other[tr]
+ path_pool_other[tr] << [x1, y1]
+ else
+ path_pool_other[tr] = [[x1, y1]]
+ end
+ else
+ path_pool_source[tr] = [x1, y1]
+ end
+ path_flags << tr
+ if path_flags.tally.any?{|k, v| v > 2}
+ raise RSTError, "Error: input text contains a path having more than two ends:\n > #{tr}"
+ end
+ end
+ end
- line_data = @line_styles.sub(/X1/, fromLeft.to_s)
- line_data = line_data.sub(/Y1/, fromTop.to_s)
- line_data = line_data.sub(/X2/, toLeft.to_s)
- @tree_data += line_data.sub(/Y2/, toBot.to_s)
+ path_flags.tally.each do |k, v|
+ if v == 1
+ raise RSTError, "Error: input text contains a path having only one end:\n > #{k}"
+ end
+ end
- end
+ paths = []
+ path_pool_source.each do |k, v|
+ path_flags.delete(k)
+ if targets = path_pool_target[k]
+ targets.each do |t|
+ paths << {x1: v[0], y1: v[1], x2: t[0], y2: t[1], arrow: :single}
+ end
+ elsif others = path_pool_other[k]
+ others.each do |t|
+ paths << {x1: v[0], y1: v[1], x2: t[0], y2: t[1], arrow: :none}
+ end
+ end
+ end
- # Draw a triangle between child/parent elements
- def triangle_to_parent(fromX, fromY, fromW, textW, symmetrize = true)
- if (fromY == 0)
- return
- end
+ path_flags.uniq.each do |k|
+ targets = path_pool_target[k]
+ fst = targets.shift
+ targets.each do |t|
+ paths << {x1: fst[0], y1: fst[1], x2: t[0], y2: t[1], arrow: :double}
+ end
+ end
- toX = fromX
- fromCenter = (fromX + fromW / 2 + @m[:b_side])
+ paths.each do |t|
+ draw_a_path(t[:x1], t[:y1] + $height_connector_to_text / 2,
+ t[:x2], t[:y2] + $height_connector_to_text / 2,
+ t[:arrow])
+ end
- fromTop = row2px(fromY)
- fromLeft1 = (fromCenter + textW / 2)
- fromLeft2 = (fromCenter - textW / 2)
- toBot = (row2px(fromY - 1) + @e_height)
+ paths.size
+ end
- if symmetrize
- toLeft = (toX + textW / 2 + @m[:b_side])
- else
- toLeft = (toX + textW / 2 + @m[:b_side] * 3)
+ def generate_line(x1, y1, x2, y2, col, dashed = false, arrow = false, stroke_width = 1)
+ if arrow
+ string = "marker-end='url(#arrow)' "
+ else
+ string = ""
+ end
+ dasharray = dashed ? "stroke-dasharray='8 8'" : ""
+ swidth = FONT_SCALING * stroke_width
+
+ "<line x1='#{x1}' y1='#{y1}' x2='#{x2}' y2='#{y2}' style='fill: none; stroke: #{col}; stroke-width:#{swidth}' #{dasharray} #{string}/>"
end
- polygon_data = @polygon_styles.sub(/X1/, fromLeft1.to_s)
- polygon_data = polygon_data.sub(/Y1/, fromTop.to_s)
- polygon_data = polygon_data.sub(/X2/, fromLeft2.to_s)
- polygon_data = polygon_data.sub(/Y2/, fromTop.to_s)
- polygon_data = polygon_data.sub(/X3/, toLeft.to_s)
- @tree_data += polygon_data.sub(/Y3/, toBot.to_s)
- end
+ # Draw a line between child/parent elements
+ def line_to_parent(parent, child)
+ if (child.horizontal_indent == 0 )
+ return
+ end
- # If a node element text is wider than the sum of it's
- # child elements, then the child elements need to
- # be resized to even out the space. This function
- # recurses down the a child tree and sizes the
- # children appropriately.
- def fix_child_size(id, current, target)
- children = @e_list.get_children(id)
- @e_list.set_element_width(id, target)
+ x1 = child.horizontal_indent + child.content_width / 2
+ y1 = child.vertical_indent + $height_connector_to_text / 2
+ x2 = parent.horizontal_indent + parent.content_width / 2
+ y2 = parent.vertical_indent + parent.content_height + $height_connector_to_text
- if(children.length > 0 )
- delta = target - current
- target_delta = delta / children.length
+ line_data = @line_styles.sub(/X1/, x1.to_s)
+ line_data = line_data.sub(/Y1/, y1.to_s)
+ line_data = line_data.sub(/X2/, x2.to_s)
+ @tree_data += line_data.sub(/Y2/, y2.to_s)
+ end
- children.each do |child|
- child_width = @e_list.get_element_width(child)
- fix_child_size(child, child_width, child_width + target_delta)
+ # Draw a triangle between child/parent elements
+ def triangle_to_parent(parent, child)
+ if (child.horizontal_indent == 0)
+ return
end
- end
- end
- def img_get_txt_width(text, font, font_size, multiline = true)
- parts = text.split("_", 2)
- main_before = parts[0].strip
- sub = parts[1]
- main = get_txt_only(main_before)
- main_metrics = img_get_txt_metrics(main, font, font_size, multiline)
- width = main_metrics.width
- if sub
- sub_metrics = img_get_txt_metrics(sub.strip, font, font_size * SUBSCRIPT_CONST, multiline)
- width += sub_metrics.width
+ x1 = child.horizontal_indent
+ y1 = child.vertical_indent + $height_connector_to_text / 2
+ x2 = child.horizontal_indent + child.content_width
+ y2 = child.vertical_indent + $height_connector_to_text / 2
+ x3 = parent.horizontal_indent + parent.content_width / 2
+ y3 = parent.vertical_indent + parent.content_height + $height_connector_to_text
+
+ polygon_data = @polygon_styles.sub(/X1/, x1.to_s)
+ polygon_data = polygon_data.sub(/Y1/, y1.to_s)
+ polygon_data = polygon_data.sub(/X2/, x2.to_s)
+ polygon_data = polygon_data.sub(/Y2/, y2.to_s)
+ polygon_data = polygon_data.sub(/X3/, x3.to_s)
+ @tree_data += polygon_data.sub(/Y3/, y3.to_s)
end
- return width
end
-
- def img_get_txt_height(text, font, font_size)
- main_metrics = img_get_txt_metrics(text, font, font_size, false)
- main_metrics.height
- end
-
end