rsyntaxtree 0.9.3 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.tags +203 -0
- data/Gemfile +2 -0
- data/README.md +0 -1
- data/Rakefile +7 -0
- data/bin/rsyntaxtree +38 -31
- data/fonts/OpenMoji-Black.ttf +0 -0
- data/fonts/OpenMoji-Color.ttf +0 -0
- data/lib/rsyntaxtree/base_graph.rb +262 -0
- data/lib/rsyntaxtree/element.rb +155 -25
- data/lib/rsyntaxtree/elementlist.rb +16 -13
- data/lib/rsyntaxtree/markup_parser.rb +208 -0
- data/lib/rsyntaxtree/string_parser.rb +190 -204
- data/lib/rsyntaxtree/svg_graph.rb +445 -299
- data/lib/rsyntaxtree/utils.rb +49 -6
- data/lib/rsyntaxtree/version.rb +1 -1
- data/lib/rsyntaxtree.rb +143 -161
- data/rsyntaxtree.gemspec +2 -0
- data/test/markup_parser_test.rb +207 -0
- metadata +37 -11
- data/fonts/latinmodern-math.otf +0 -0
- data/fonts/lmroman10-bold.otf +0 -0
- data/fonts/lmroman10-bolditalic.otf +0 -0
- data/fonts/lmroman10-italic.otf +0 -0
- data/fonts/lmroman10-regular.otf +0 -0
- data/lib/rsyntaxtree/error_message.rb +0 -68
- data/lib/rsyntaxtree/graph.rb +0 -312
- data/lib/rsyntaxtree/tree_graph.rb +0 -327
@@ -6,358 +6,504 @@
|
|
6
6
|
#==========================
|
7
7
|
#
|
8
8
|
# Parses an element list into an SVG tree.
|
9
|
-
#
|
10
|
-
# This file is part of RSyntaxTree, which is a ruby port of Andre Eisenbach's
|
11
|
-
# excellent program phpSyntaxTree.
|
12
|
-
#
|
13
9
|
# Copyright (c) 2007-2021 Yoichiro Hasebe <yohasebe@gmail.com>
|
14
|
-
# Copyright (c) 2003-2004 Andre Eisenbach <andre@ironcreek.net>
|
15
10
|
|
16
11
|
require "tempfile"
|
17
|
-
require '
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
@
|
31
|
-
@
|
32
|
-
|
33
|
-
@
|
34
|
-
@
|
35
|
-
|
36
|
-
@
|
37
|
-
@
|
12
|
+
require 'base_graph'
|
13
|
+
require 'utils'
|
14
|
+
|
15
|
+
module RSyntaxTree
|
16
|
+
class SVGGraph < BaseGraph
|
17
|
+
attr_accessor :width, :height
|
18
|
+
|
19
|
+
def initialize(element_list, params)
|
20
|
+
@height = 0
|
21
|
+
@width = 0
|
22
|
+
@extra_lines = []
|
23
|
+
@fontset = params[:fontset]
|
24
|
+
@fontsize = params[:fontsize]
|
25
|
+
@transparent = params[:transparent]
|
26
|
+
@color = params[:color]
|
27
|
+
@fontstyle = params[:fontstyle]
|
28
|
+
@margin = params[:margin].to_i
|
29
|
+
@line_styles = "<line style='stroke:black; stroke-width:#{FONT_SCALING};' x1='X1' y1='Y1' x2='X2' y2='Y2' />\n"
|
30
|
+
@polygon_styles = "<polygon style='fill: none; stroke: black; stroke-width:#{FONT_SCALING};' points='X1 Y1 X2 Y2 X3 Y3' />\n"
|
31
|
+
@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"
|
32
|
+
@tree_data = String.new
|
33
|
+
@visited_x = {}
|
34
|
+
@visited_y = {}
|
35
|
+
super(element_list, params)
|
38
36
|
end
|
39
37
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
@line_styles = "<line style='stroke:black; stroke-width:#{FONT_SCALING};' x1='X1' y1='Y1' x2='X2' y2='Y2' />\n"
|
45
|
-
@polygon_styles = "<polygon style='fill: none; stroke: black; stroke-width:#{FONT_SCALING};' points='X1 Y1 X2 Y2 X3 Y3' />\n"
|
46
|
-
@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"
|
47
|
-
@tree_data = String.new
|
48
|
-
end
|
38
|
+
def svg_data
|
39
|
+
metrics = parse_list
|
40
|
+
@height = metrics[:height] + @margin * 2
|
41
|
+
@width = metrics[:width] + @margin * 2
|
49
42
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
43
|
+
x1 = 0 - @margin
|
44
|
+
y1 = 0 - @margin
|
45
|
+
x2 = @width + @margin
|
46
|
+
y2 = @height + @margin
|
47
|
+
extra_lines = @extra_lines.join("\n")
|
54
48
|
|
55
|
-
|
56
|
-
|
57
|
-
lm = get_left_most(@tree_data)
|
58
|
-
width = @width - lm + @margin * 2
|
59
|
-
height = @height + @margin * 2
|
49
|
+
as2 = $h_gap_between_nodes / 2 * 0.8
|
50
|
+
as = as2 / 2
|
60
51
|
|
61
|
-
|
52
|
+
header =<<EOD
|
62
53
|
<?xml version="1.0" standalone="no"?>
|
63
|
-
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
64
|
-
"http://www.w3.org/
|
65
|
-
<
|
66
|
-
<
|
67
|
-
<
|
68
|
-
|
69
|
-
|
70
|
-
|
54
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
55
|
+
<svg width="#{@width}" height="#{@height}" viewBox="#{x1}, #{y1}, #{x2}, #{y2}" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
56
|
+
<defs>
|
57
|
+
<marker id="arrow" markerUnits="strokeWidth" markerWidth="#{as2}" markerHeight="#{as2}" viewBox="0 0 #{as2} #{as2}" refX="#{as}" refY="0">
|
58
|
+
<polyline fill="none" stroke="#{@col_path}" stroke-width="1" points="0,#{as2},#{as},0,#{as2},#{as2}" />
|
59
|
+
</marker>
|
60
|
+
<pattern id="hatchBlack" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
|
61
|
+
<line x1="0" y="0" x2="0" y2="10" stroke="black" stroke-width="4"></line>
|
62
|
+
</pattern>
|
63
|
+
<pattern id="hatchForNode" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
|
64
|
+
<line x1="0" y="0" x2="0" y2="10" stroke="#{@col_node}" stroke-width="4"></line>
|
65
|
+
</pattern>
|
66
|
+
<pattern id="hatchForLeaf" x="10" y="10" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
|
67
|
+
<line x1="0" y="0" x2="0" y2="10" stroke="#{@col_leaf}" stroke-width="4"></line>
|
68
|
+
</pattern>
|
69
|
+
</defs>
|
71
70
|
EOD
|
72
71
|
|
73
|
-
|
74
|
-
|
75
|
-
<rect x="#{-@margin + lm}" y="-#{@margin}" width="#{@width - lm + @margin * 2}" height="#{@height + @margin * 2}" stroke="none" fill="white" />"
|
72
|
+
rect =<<EOD
|
73
|
+
<rect x="#{x1}" y="#{y1}" width="#{x2}" height="#{y2}" stroke="none" fill="white" />"
|
76
74
|
EOD
|
77
75
|
|
78
|
-
|
79
|
-
|
80
|
-
if @transparent
|
81
|
-
header + @tree_data + footer
|
82
|
-
else
|
83
|
-
header + rect + @tree_data + footer
|
84
|
-
end
|
85
|
-
end
|
76
|
+
footer = "</svg>"
|
86
77
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
num.times do |i|
|
92
|
-
begin
|
93
|
-
tfname = "#{basename}.#{$$}.#{i}.#{ext}"
|
94
|
-
tfile = File.open(tfname, flags, 0600)
|
95
|
-
rescue Errno::EEXIST
|
96
|
-
next
|
78
|
+
if @transparent
|
79
|
+
header + @tree_data + extra_lines + footer
|
80
|
+
else
|
81
|
+
header + rect + @tree_data + extra_lines + footer
|
97
82
|
end
|
98
|
-
tfile.close
|
99
|
-
return tfname
|
100
83
|
end
|
101
|
-
end
|
102
84
|
|
103
|
-
|
85
|
+
def draw_a_path(s_x, s_y, t_x, t_y, target_arrow = :none)
|
104
86
|
|
105
|
-
|
106
|
-
|
107
|
-
string = string.sub(/\^\z/){""}
|
108
|
-
# Calculate element dimensions and position
|
109
|
-
if (type == ETYPE_LEAF) and @leafstyle == "nothing"
|
110
|
-
top = row2px(y - 1) + (@font_size * 1.5)
|
111
|
-
else
|
112
|
-
top = row2px(y)
|
113
|
-
end
|
114
|
-
left = x + @m[:b_side]
|
115
|
-
bottom = top + @e_height
|
116
|
-
right = left + w
|
117
|
-
|
118
|
-
# Split the string into the main part and the
|
119
|
-
# subscript part of the element (if any)
|
120
|
-
parts = string.split("_", 2)
|
121
|
-
if(parts.length > 1 )
|
122
|
-
main = parts[0].strip
|
123
|
-
sub = parts[1].gsub(/_/, " ").strip
|
124
|
-
else
|
125
|
-
main = parts[0].strip
|
126
|
-
sub = ""
|
127
|
-
end
|
87
|
+
x_spacing = $h_gap_between_nodes * 1.25
|
88
|
+
y_spacing = $height_connector * 0.65
|
128
89
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
elsif /\A\~(.+)\~\z/ =~ main
|
136
|
-
main = $1
|
137
|
-
main_decoration= "line-through"
|
138
|
-
else
|
139
|
-
main_decoration= ""
|
140
|
-
end
|
90
|
+
ymax = [s_y, t_y].max
|
91
|
+
if ymax < @height
|
92
|
+
new_y = @height + y_spacing
|
93
|
+
else
|
94
|
+
new_y = ymax + y_spacing
|
95
|
+
end
|
141
96
|
|
142
|
-
if /\A\*\*\*(.+)\*\*\*\z/ =~ main
|
143
|
-
main = $1
|
144
|
-
main_style = "font-style: italic"
|
145
|
-
main_weight = "font-weight: bold"
|
146
|
-
elsif /\A\*\*(.+)\*\*\z/ =~ main
|
147
|
-
main = $1
|
148
|
-
main_style = ""
|
149
|
-
main_weight = "font-weight: bold"
|
150
|
-
elsif /\A\*(.+)\*\z/ =~ main
|
151
|
-
main = $1
|
152
|
-
main_style = "font-style: italic"
|
153
|
-
main_weight = ""
|
154
|
-
else
|
155
|
-
main_style = ""
|
156
|
-
main_weight = ""
|
157
|
-
end
|
158
97
|
|
159
|
-
|
160
|
-
|
161
|
-
|
98
|
+
if @visited_x[s_x]
|
99
|
+
new_s_x = s_x - x_spacing * @visited_x[s_x]
|
100
|
+
@visited_x[s_x] += 1
|
101
|
+
else
|
102
|
+
new_s_x = s_x
|
103
|
+
@visited_x[s_x] = 1
|
104
|
+
end
|
162
105
|
|
163
|
-
|
164
|
-
|
165
|
-
|
106
|
+
if @visited_x[t_x]
|
107
|
+
new_t_x = t_x - x_spacing * @visited_x[t_x]
|
108
|
+
@visited_x[t_x] += 1
|
109
|
+
else
|
110
|
+
new_t_x = t_x
|
111
|
+
@visited_x[t_x] = 1
|
112
|
+
end
|
166
113
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
l_width = img_get_txt_width(l, @font, @font_size)
|
171
|
-
main_width = l_width if main_width < l_width
|
172
|
-
main_height += img_get_txt_height(l, @font, @font_size)
|
173
|
-
end
|
114
|
+
s_y += $h_gap_between_nodes / 2
|
115
|
+
t_y += $h_gap_between_nodes / 2
|
116
|
+
new_y += $h_gap_between_nodes / 2
|
174
117
|
|
118
|
+
dashed = true if target_arrow == :none
|
175
119
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
elsif
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
sub = $1
|
185
|
-
sub_decoration= "line-through"
|
120
|
+
if target_arrow == :single
|
121
|
+
@extra_lines << generate_line(new_s_x, s_y, new_s_x, new_y, @col_path, dashed)
|
122
|
+
@extra_lines << generate_line(new_s_x, new_y, new_t_x, new_y, @col_path, dashed)
|
123
|
+
@extra_lines << generate_line(new_t_x, new_y, new_t_x, t_y, @col_path ,dashed, true)
|
124
|
+
elsif target_arrow == :double
|
125
|
+
@extra_lines << generate_line(new_s_x, new_y, new_s_x, s_y, @col_path, dashed, true)
|
126
|
+
@extra_lines << generate_line(new_s_x, new_y, new_t_x, new_y, @col_path, dashed)
|
127
|
+
@extra_lines << generate_line(new_t_x, new_y, new_t_x, t_y, @col_path ,dashed, true)
|
186
128
|
else
|
187
|
-
|
129
|
+
@extra_lines << generate_line(new_s_x, s_y, new_s_x, new_y, @col_path, dashed)
|
130
|
+
@extra_lines << generate_line(new_s_x, new_y, new_t_x, new_y, @col_path, dashed)
|
131
|
+
@extra_lines << generate_line(new_t_x, new_y, new_t_x, t_y, @col_path ,dashed)
|
188
132
|
end
|
189
133
|
|
190
|
-
if
|
191
|
-
sub = $1
|
192
|
-
sub_style = "font-style: italic"
|
193
|
-
sub_weight = "font-weight: bold"
|
194
|
-
elsif /\A\*\*(.+)\*\*\z/ =~ sub
|
195
|
-
sub = $1
|
196
|
-
sub_style = ""
|
197
|
-
sub_weight = "font-weight: bold"
|
198
|
-
elsif /\A\*(.+)\*\z/ =~ sub
|
199
|
-
sub = $1
|
200
|
-
sub_style = "font-style: italic"
|
201
|
-
sub_weight = ""
|
202
|
-
else
|
203
|
-
sub_style = ""
|
204
|
-
sub_weight = ""
|
205
|
-
end
|
206
|
-
sub_height = img_get_txt_height(sub, @font, @font_size)
|
207
|
-
sub_width = img_get_txt_width(sub.to_s, @font, @sub_size)
|
208
|
-
else
|
209
|
-
sub_width = 0
|
210
|
-
sub_height = 0
|
134
|
+
@height = new_y if new_y > @height
|
211
135
|
end
|
212
136
|
|
213
|
-
|
214
|
-
|
215
|
-
end
|
137
|
+
def draw_element(element)
|
138
|
+
top = element.vertical_indent
|
216
139
|
|
217
|
-
|
218
|
-
|
140
|
+
left = element.horizontal_indent
|
141
|
+
bottom = top +$single_line_height
|
142
|
+
right = left + element.content_width
|
219
143
|
|
220
|
-
|
221
|
-
if(type == ETYPE_LEAF)
|
222
|
-
col = @col_leaf
|
223
|
-
else
|
224
|
-
col = @col_node
|
225
|
-
end
|
144
|
+
txt_pos = left + (right - left) / 2
|
226
145
|
|
227
|
-
|
228
|
-
|
229
|
-
|
146
|
+
if(element.type == ETYPE_LEAF)
|
147
|
+
col = @col_leaf
|
148
|
+
else
|
149
|
+
col = @col_node
|
150
|
+
end
|
230
151
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
152
|
+
text_data = @text_styles.sub(/COLOR/, col)
|
153
|
+
text_data = text_data.sub(/fontsize/, @fontsize.to_s + "px;")
|
154
|
+
text_x = txt_pos - element.content_width / 2
|
155
|
+
text_y = top + $single_line_height - $height_connector_to_text
|
156
|
+
text_data = text_data.sub(/X_VALUE/, text_x.to_s)
|
157
|
+
text_data = text_data.sub(/Y_VALUE/, text_y.to_s)
|
158
|
+
new_text = ""
|
159
|
+
this_x = 0
|
160
|
+
this_y = 0
|
161
|
+
bc = {:x => text_x - $h_gap_between_nodes / 2 , :y => top, :width => element.content_width + $h_gap_between_nodes, :height => nil}
|
162
|
+
element.content.each_with_index do |l, idx|
|
163
|
+
case l[:type]
|
164
|
+
when :border, :bborder
|
165
|
+
x1 = text_x
|
166
|
+
if idx == 0
|
167
|
+
text_y -= l[:height]
|
168
|
+
elsif
|
169
|
+
text_y += l[:height]
|
170
|
+
end
|
171
|
+
y1 = text_y - $single_line_height / 8
|
172
|
+
x2 = text_x + element.content_width
|
173
|
+
y2 = y1
|
174
|
+
this_width = x2 - x1
|
175
|
+
case l[:type]
|
176
|
+
when :border
|
177
|
+
stroke_width = FONT_SCALING
|
178
|
+
when :bborder
|
179
|
+
stroke_width = FONT_SCALING * 2
|
180
|
+
end
|
181
|
+
@extra_lines << "<line style=\"stroke:#{col}; stroke-width:#{stroke_width}; \" x1=\"#{x1}\" y1=\"#{y1}\" x2=\"#{x2}\" y2=\"#{y2}\"></line>"
|
245
182
|
else
|
246
|
-
|
247
|
-
|
183
|
+
if element.enclosure == :brackets
|
184
|
+
this_x = txt_pos - element.content_width / 2
|
185
|
+
else
|
186
|
+
ewidth = 0
|
187
|
+
l[:elements].each do |e|
|
188
|
+
ewidth += e[:width]
|
189
|
+
end
|
190
|
+
this_x = txt_pos - (ewidth / 2)
|
191
|
+
end
|
192
|
+
text_y += l[:elements].map{|e| e[:height]}.max if idx != 0
|
193
|
+
|
194
|
+
l[:elements].each_with_index do |e, idx|
|
195
|
+
escaped_text = e[:text].gsub('>', '>').gsub('<', '<');
|
196
|
+
decorations = []
|
197
|
+
if e[:decoration].include?(:overline)
|
198
|
+
decorations << "overline"
|
199
|
+
end
|
200
|
+
|
201
|
+
if e[:decoration].include?(:underline)
|
202
|
+
decorations << "underline"
|
203
|
+
end
|
204
|
+
|
205
|
+
if e[:decoration].include?(:linethrough)
|
206
|
+
decorations << "line-through"
|
207
|
+
end
|
208
|
+
decoration ="text-decoration=\"" + decorations.join(" ") + "\""
|
209
|
+
|
210
|
+
style = "style=\""
|
211
|
+
if e[:decoration].include?(:small)
|
212
|
+
style += "font-size: #{(SUBSCRIPT_CONST.to_f * 100).to_i}%; "
|
213
|
+
this_y = text_y - (($single_X_metrics.height - $single_X_metrics.height * SUBSCRIPT_CONST) / 4) + 2
|
214
|
+
elsif e[:decoration].include?(:superscript)
|
215
|
+
style += "font-size: #{(SUBSCRIPT_CONST.to_f * 100).to_i}%; "
|
216
|
+
this_y = text_y - ($single_X_metrics.height / 4) + 1
|
217
|
+
elsif e[:decoration].include?(:subscript)
|
218
|
+
style += "font-size: #{(SUBSCRIPT_CONST.to_f * 100).to_i}%; "
|
219
|
+
this_y = text_y + 4
|
220
|
+
else
|
221
|
+
this_y = text_y
|
222
|
+
end
|
223
|
+
|
224
|
+
if e[:decoration].include?(:bold) || e[:decoration].include?(:bolditalic)
|
225
|
+
style += "font-weight: bold; "
|
226
|
+
end
|
227
|
+
|
228
|
+
if e[:decoration].include?(:italic) || e[:decoration].include?(:bolditalic)
|
229
|
+
style += "font-style: italic; "
|
230
|
+
end
|
231
|
+
|
232
|
+
style += "\""
|
233
|
+
|
234
|
+
case @fontstyle
|
235
|
+
when /(?:cjk)/
|
236
|
+
fontstyle = "'WenQuanYi Zen Hei', 'Noto Sans', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', sans-serif"
|
237
|
+
when /(?:sans)/
|
238
|
+
if e[:cjk]
|
239
|
+
fontstyle = "'Noto Sans JP', 'Noto Sans', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', sans-serif"
|
240
|
+
else
|
241
|
+
fontstyle = "'Noto Sans', 'Noto Sans JP', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', sans-serif"
|
242
|
+
end
|
243
|
+
when /(?:serif)/
|
244
|
+
if e[:cjk]
|
245
|
+
fontstyle = "'Noto Serif JP', 'Noto Serif', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', serif"
|
246
|
+
else
|
247
|
+
fontstyle = "'Noto Serif', 'Noto Serif JP', OpenMoji, 'OpenMoji Color', 'OpenMoji Black', serif"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
if e[:decoration].include?(:box) || e[:decoration].include?(:circle) || e[:decoration].include?(:bar)
|
252
|
+
enc_height = e[:height]
|
253
|
+
enc_y = this_y - e[:height] * 0.8 + FONT_SCALING
|
254
|
+
|
255
|
+
if e[:text].size == 1
|
256
|
+
enc_width = e[:width]
|
257
|
+
enc_x = this_x
|
258
|
+
else
|
259
|
+
enc_width = e[:width]
|
260
|
+
enc_x = this_x
|
261
|
+
end
|
262
|
+
|
263
|
+
if e[:decoration].include?(:hatched)
|
264
|
+
case element.type
|
265
|
+
when ETYPE_LEAF
|
266
|
+
if @color
|
267
|
+
fill = "url(#hatchForLeaf)"
|
268
|
+
else
|
269
|
+
fill = "url(#hatchBlack)"
|
270
|
+
end
|
271
|
+
when ETYPE_NODE
|
272
|
+
if @color
|
273
|
+
fill = "url(#hatchForNode)"
|
274
|
+
else
|
275
|
+
fill = "url(#hatchBlack)"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
else
|
279
|
+
fill = "none"
|
280
|
+
end
|
281
|
+
|
282
|
+
enc = nil
|
283
|
+
bar = nil
|
284
|
+
|
285
|
+
if e[:decoration].include?(:bstroke)
|
286
|
+
stroke_width = FONT_SCALING * 2.5
|
287
|
+
else
|
288
|
+
stroke_width = FONT_SCALING
|
289
|
+
end
|
290
|
+
|
291
|
+
if e[:decoration].include?(:box)
|
292
|
+
enc = "<rect style='stroke: #{col}; stroke-width:#{stroke_width};'
|
293
|
+
x='#{enc_x}' y='#{enc_y}'
|
294
|
+
width='#{enc_width}' height='#{enc_height}'
|
295
|
+
fill='#{fill}' />\n"
|
296
|
+
elsif e[:decoration].include?(:circle)
|
297
|
+
enc = "<rect style='stroke: #{col}; stroke-width:#{stroke_width};'
|
298
|
+
x='#{enc_x}' y='#{enc_y}' rx='#{enc_height / 2}' ry='#{enc_height / 2}'
|
299
|
+
width='#{enc_width}' height='#{enc_height}'
|
300
|
+
fill='#{fill}' />\n"
|
301
|
+
elsif e[:decoration].include?(:bar)
|
302
|
+
x1 = enc_x
|
303
|
+
y1 = enc_y + enc_height / 2
|
304
|
+
x2 = enc_x + enc_width
|
305
|
+
y2 = y1
|
306
|
+
ar_hwidth = e[:width] / 4.0
|
307
|
+
bar = "<line style='stroke:#{col}; stroke-width:#{stroke_width};' x1='#{x1}' y1='#{y1}' x2='#{x2}' y2='#{y2}'></line>\n"
|
308
|
+
@extra_lines << bar
|
309
|
+
|
310
|
+
if e[:decoration].include?(:arrow_to_l)
|
311
|
+
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"
|
312
|
+
@extra_lines << l_arrowhead
|
313
|
+
end
|
314
|
+
|
315
|
+
if e[:decoration].include?(:arrow_to_r)
|
316
|
+
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"
|
317
|
+
@extra_lines << r_arrowhead
|
318
|
+
end
|
319
|
+
|
320
|
+
|
321
|
+
end
|
322
|
+
|
323
|
+
@extra_lines << enc if enc
|
324
|
+
|
325
|
+
if e[:text].size == 1
|
326
|
+
this_x += (e[:height] - e[:content_width]) / 2
|
327
|
+
else
|
328
|
+
this_x += $width_half_X / 2
|
329
|
+
end
|
330
|
+
new_text << set_tspan(this_x, this_y, style, decoration, fontstyle, escaped_text)
|
331
|
+
if e[:text].size == 1
|
332
|
+
this_x += e[:content_width]
|
333
|
+
this_x += (e[:height] - e[:content_width]) / 2
|
334
|
+
else
|
335
|
+
this_x += e[:content_width]
|
336
|
+
this_x += $width_half_X / 2
|
337
|
+
end
|
338
|
+
|
339
|
+
elsif e[:decoration].include?(:whitespace)
|
340
|
+
this_x += e[:width]
|
341
|
+
next
|
342
|
+
else
|
343
|
+
new_text << set_tspan(this_x, this_y, style, decoration, fontstyle, escaped_text)
|
344
|
+
this_x += e[:width]
|
345
|
+
end
|
346
|
+
|
347
|
+
end
|
248
348
|
end
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
349
|
+
@height = text_y if text_y > @height
|
350
|
+
end
|
351
|
+
bc[:y] = bc[:y] + $height_connector_to_text * 3 / 4
|
352
|
+
bc[:height] = text_y - bc[:y] + $height_connector_to_text
|
353
|
+
if element.enclosure == :brackets
|
354
|
+
@extra_lines << generate_line(bc[:x], bc[:y], bc[:x] + $h_gap_between_nodes / 2, bc[:y], col)
|
355
|
+
@extra_lines << generate_line(bc[:x], bc[:y], bc[:x], bc[:y] + bc[:height], col)
|
356
|
+
@extra_lines << generate_line(bc[:x], bc[:y] + bc[:height], bc[:x] + $h_gap_between_nodes / 2, bc[:y] + bc[:height], col)
|
357
|
+
@extra_lines << generate_line(bc[:x] + bc[:width], bc[:y], bc[:x] + bc[:width] - $h_gap_between_nodes / 2, bc[:y], col)
|
358
|
+
@extra_lines << generate_line(bc[:x] + bc[:width], bc[:y], bc[:x] + bc[:width], bc[:y] + bc[:height], col)
|
359
|
+
@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)
|
360
|
+
elsif element.enclosure == :rectangle
|
361
|
+
@extra_lines << generate_line(bc[:x], bc[:y], bc[:x] + bc[:width], bc[:y], col)
|
362
|
+
@extra_lines << generate_line(bc[:x], bc[:y], bc[:x], bc[:y] + bc[:height], col)
|
363
|
+
@extra_lines << generate_line(bc[:x] + bc[:width], bc[:y], bc[:x] + bc[:width], bc[:y] + bc[:height], col)
|
364
|
+
@extra_lines << generate_line(bc[:x], bc[:y] + bc[:height], bc[:x] + bc[:width], bc[:y] + bc[:height], col)
|
365
|
+
elsif element.enclosure == :brectangle
|
366
|
+
@extra_lines << generate_line(bc[:x], bc[:y], bc[:x] + bc[:width], bc[:y], col, false, false, 2)
|
367
|
+
@extra_lines << generate_line(bc[:x], bc[:y], bc[:x], bc[:y] + bc[:height], col, false, false, 2)
|
368
|
+
@extra_lines << generate_line(bc[:x] + bc[:width], bc[:y], bc[:x] + bc[:width], bc[:y] + bc[:height], col, false, false, 2)
|
369
|
+
@extra_lines << generate_line(bc[:x], bc[:y] + bc[:height], bc[:x] + bc[:width], bc[:y] + bc[:height], col, false, false, 2)
|
253
370
|
end
|
254
|
-
main = new_main
|
255
|
-
end
|
256
|
-
@tree_data += main_data.sub(/TD/, "text-decoration='#{main_decoration}'")
|
257
|
-
.sub(/ST/, main_style + ";")
|
258
|
-
.sub(/WA/, main_weight + ";")
|
259
|
-
.sub(/CONTENT/, main)
|
260
|
-
|
261
|
-
# Draw subscript text
|
262
|
-
if sub && sub != ""
|
263
|
-
sub_data = @text_styles.sub(/COLOR/, col)
|
264
|
-
sub_data = sub_data.sub(/FONT_SIZE/, @sub_size.to_s)
|
265
|
-
sub_x = txt_pos + (main_width / 2) - (sub_width / 2)
|
266
|
-
sub_y = main_y + sub_height / 6
|
267
|
-
sub_data = sub_data.sub(/X_VALUE/, sub_x.to_s)
|
268
|
-
sub_data = sub_data.sub(/Y_VALUE/, sub_y.to_s)
|
269
|
-
@tree_data += sub_data.sub(/TD/, "text-decoration='#{sub_decoration}'")
|
270
|
-
.sub(/ST/, sub_style)
|
271
|
-
.sub(/WA/, sub_weight)
|
272
|
-
.sub(/CONTENT/, sub)
|
273
|
-
@height += sub_height / 4
|
274
|
-
end
|
275
|
-
end
|
276
371
|
|
277
|
-
|
278
|
-
|
372
|
+
element.content_height = bc[:height]
|
373
|
+
@tree_data += text_data.sub(/CONTENT/, new_text)
|
374
|
+
end
|
279
375
|
|
280
|
-
|
281
|
-
|
376
|
+
def set_tspan(this_x, this_y, style, decoration, fontstyle, text)
|
377
|
+
text.gsub!(/■+/) do |x|
|
378
|
+
num_spaces = x.size
|
379
|
+
"<tspan style='fill:none;'>" + "■" * num_spaces + "</tspan>"
|
380
|
+
end
|
381
|
+
"<tspan x='#{this_x}' y='#{this_y}' #{style} #{decoration} font-family=\"#{fontstyle}\">#{text}</tspan>\n"
|
282
382
|
end
|
283
383
|
|
284
|
-
fromTop = row2px(fromY)
|
285
|
-
fromLeft = (fromX + fromW / 2 + @m[:b_side])
|
286
|
-
toBot = (row2px(fromY - 1 ) + @e_height)
|
287
|
-
toLeft = (toX + toW / 2 + @m[:b_side])
|
288
384
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
385
|
+
def draw_paths
|
386
|
+
rockbottom = 0
|
387
|
+
path_pool_target = {}
|
388
|
+
path_pool_other = {}
|
389
|
+
path_pool_source = {}
|
390
|
+
path_flags = []
|
391
|
+
elist = @element_list.get_elements.reverse
|
392
|
+
|
393
|
+
elist.each do |element|
|
394
|
+
x1 = element.horizontal_indent + element.content_width / 2
|
395
|
+
y1 = element.vertical_indent + element.content_height
|
396
|
+
y1 += $height_connector_to_text if element.enclosure != :none
|
397
|
+
et = element.path
|
398
|
+
et.each do |tr|
|
399
|
+
if /\A>(\d+)\z/ =~ tr
|
400
|
+
tr = $1
|
401
|
+
if path_pool_target[tr]
|
402
|
+
path_pool_target[tr] << [x1, y1]
|
403
|
+
else
|
404
|
+
path_pool_target[tr] = [[x1, y1]]
|
405
|
+
end
|
406
|
+
elsif path_pool_source[tr]
|
407
|
+
if path_pool_other[tr]
|
408
|
+
path_pool_other[tr] << [x1, y1]
|
409
|
+
else
|
410
|
+
path_pool_other[tr] = [[x1, y1]]
|
411
|
+
end
|
412
|
+
else
|
413
|
+
path_pool_source[tr] = [x1, y1]
|
414
|
+
end
|
415
|
+
path_flags << tr
|
416
|
+
if path_flags.tally.any?{|k, v| v > 2}
|
417
|
+
raise RSTError, "Error: input text contains a path having more than two ends:\n > #{tr}"
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
293
421
|
|
294
|
-
|
422
|
+
path_flags.tally.each do |k, v|
|
423
|
+
if v == 1
|
424
|
+
raise RSTError, "Error: input text contains a path having only one end:\n > #{k}"
|
425
|
+
end
|
426
|
+
end
|
295
427
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
428
|
+
paths = []
|
429
|
+
path_pool_source.each do |k, v|
|
430
|
+
path_flags.delete(k)
|
431
|
+
if targets = path_pool_target[k]
|
432
|
+
targets.each do |t|
|
433
|
+
paths << {x1: v[0], y1: v[1], x2: t[0], y2: t[1], arrow: :single}
|
434
|
+
end
|
435
|
+
elsif others = path_pool_other[k]
|
436
|
+
others.each do |t|
|
437
|
+
paths << {x1: v[0], y1: v[1], x2: t[0], y2: t[1], arrow: :none}
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
301
441
|
|
302
|
-
|
303
|
-
|
442
|
+
path_flags.uniq.each do |k|
|
443
|
+
targets = path_pool_target[k]
|
444
|
+
fst = targets.shift
|
445
|
+
targets.each do |t|
|
446
|
+
paths << {x1: fst[0], y1: fst[1], x2: t[0], y2: t[1], arrow: :double}
|
447
|
+
end
|
448
|
+
end
|
304
449
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
450
|
+
paths.each do |t|
|
451
|
+
draw_a_path(t[:x1], t[:y1] + $height_connector_to_text / 2,
|
452
|
+
t[:x2], t[:y2] + $height_connector_to_text / 2,
|
453
|
+
t[:arrow])
|
454
|
+
end
|
309
455
|
|
310
|
-
|
311
|
-
toLeft = (toX + textW / 2 + @m[:b_side])
|
312
|
-
else
|
313
|
-
toLeft = (toX + textW / 2 + @m[:b_side] * 3)
|
456
|
+
paths.size
|
314
457
|
end
|
315
458
|
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
@tree_data += polygon_data.sub(/Y3/, toBot.to_s)
|
322
|
-
end
|
323
|
-
|
324
|
-
# If a node element text is wider than the sum of it's
|
325
|
-
# child elements, then the child elements need to
|
326
|
-
# be resized to even out the space. This function
|
327
|
-
# recurses down the a child tree and sizes the
|
328
|
-
# children appropriately.
|
329
|
-
def fix_child_size(id, current, target)
|
330
|
-
children = @e_list.get_children(id)
|
331
|
-
@e_list.set_element_width(id, target)
|
332
|
-
|
333
|
-
if(children.length > 0 )
|
334
|
-
delta = target - current
|
335
|
-
target_delta = delta / children.length
|
336
|
-
|
337
|
-
children.each do |child|
|
338
|
-
child_width = @e_list.get_element_width(child)
|
339
|
-
fix_child_size(child, child_width, child_width + target_delta)
|
459
|
+
def generate_line(x1, y1, x2, y2, col, dashed = false, arrow = false, stroke_width = 1)
|
460
|
+
if arrow
|
461
|
+
string = "marker-end='url(#arrow)' "
|
462
|
+
else
|
463
|
+
string = ""
|
340
464
|
end
|
465
|
+
dasharray = dashed ? "stroke-dasharray='8 8'" : ""
|
466
|
+
swidth = FONT_SCALING * stroke_width
|
467
|
+
|
468
|
+
"<line x1='#{x1}' y1='#{y1}' x2='#{x2}' y2='#{y2}' style='fill: none; stroke: #{col}; stroke-width:#{swidth}' #{dasharray} #{string}/>"
|
341
469
|
end
|
342
|
-
end
|
343
470
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
471
|
+
# Draw a line between child/parent elements
|
472
|
+
def line_to_parent(parent, child)
|
473
|
+
if (child.horizontal_indent == 0 )
|
474
|
+
return
|
475
|
+
end
|
476
|
+
|
477
|
+
x1 = child.horizontal_indent + child.content_width / 2
|
478
|
+
y1 = child.vertical_indent + $height_connector_to_text / 2
|
479
|
+
x2 = parent.horizontal_indent + parent.content_width / 2
|
480
|
+
y2 = parent.vertical_indent + parent.content_height + $height_connector_to_text
|
481
|
+
|
482
|
+
line_data = @line_styles.sub(/X1/, x1.to_s)
|
483
|
+
line_data = line_data.sub(/Y1/, y1.to_s)
|
484
|
+
line_data = line_data.sub(/X2/, x2.to_s)
|
485
|
+
@tree_data += line_data.sub(/Y2/, y2.to_s)
|
354
486
|
end
|
355
|
-
return width
|
356
|
-
end
|
357
487
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
488
|
+
# Draw a triangle between child/parent elements
|
489
|
+
def triangle_to_parent(parent, child)
|
490
|
+
if (child.horizontal_indent == 0)
|
491
|
+
return
|
492
|
+
end
|
362
493
|
|
494
|
+
x1 = child.horizontal_indent
|
495
|
+
y1 = child.vertical_indent + $height_connector_to_text / 2
|
496
|
+
x2 = child.horizontal_indent + child.content_width
|
497
|
+
y2 = child.vertical_indent + $height_connector_to_text / 2
|
498
|
+
x3 = parent.horizontal_indent + parent.content_width / 2
|
499
|
+
y3 = parent.vertical_indent + parent.content_height + $height_connector_to_text
|
500
|
+
|
501
|
+
polygon_data = @polygon_styles.sub(/X1/, x1.to_s)
|
502
|
+
polygon_data = polygon_data.sub(/Y1/, y1.to_s)
|
503
|
+
polygon_data = polygon_data.sub(/X2/, x2.to_s)
|
504
|
+
polygon_data = polygon_data.sub(/Y2/, y2.to_s)
|
505
|
+
polygon_data = polygon_data.sub(/X3/, x3.to_s)
|
506
|
+
@tree_data += polygon_data.sub(/Y3/, y3.to_s)
|
507
|
+
end
|
508
|
+
end
|
363
509
|
end
|