rsyntaxtree 0.9.3 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.tags +179 -0
  4. data/Gemfile +2 -0
  5. data/README.md +35 -29
  6. data/Rakefile +7 -0
  7. data/bin/rsyntaxtree +42 -31
  8. data/fonts/OpenMoji-Black.ttf +0 -0
  9. data/fonts/OpenMoji-Color.ttf +0 -0
  10. data/img/elements/circle.png +0 -0
  11. data/img/elements/circle_abc.png +0 -0
  12. data/img/elements/circle_bold.png +0 -0
  13. data/img/elements/circle_hatched.png +0 -0
  14. data/img/elements/circle_one.png +0 -0
  15. data/img/elements/connector.png +0 -0
  16. data/img/elements/connector_bold.png +0 -0
  17. data/img/elements/square.png +0 -0
  18. data/img/elements/square_abc.png +0 -0
  19. data/img/elements/square_bold.png +0 -0
  20. data/img/elements/square_hatched.png +0 -0
  21. data/img/elements/square_one.png +0 -0
  22. data/img/rsyntaxtree.png +0 -0
  23. data/img/sample.png +0 -0
  24. data/img/sample.svg +38 -0
  25. data/lib/rsyntaxtree/base_graph.rb +262 -0
  26. data/lib/rsyntaxtree/element.rb +156 -25
  27. data/lib/rsyntaxtree/elementlist.rb +16 -13
  28. data/lib/rsyntaxtree/markup_parser.rb +208 -0
  29. data/lib/rsyntaxtree/string_parser.rb +187 -204
  30. data/lib/rsyntaxtree/svg_graph.rb +471 -298
  31. data/lib/rsyntaxtree/utils.rb +49 -6
  32. data/lib/rsyntaxtree/version.rb +1 -1
  33. data/lib/rsyntaxtree.rb +144 -161
  34. data/rsyntaxtree.gemspec +2 -0
  35. data/test/markup_parser_test.rb +207 -0
  36. metadata +52 -11
  37. data/fonts/latinmodern-math.otf +0 -0
  38. data/fonts/lmroman10-bold.otf +0 -0
  39. data/fonts/lmroman10-bolditalic.otf +0 -0
  40. data/fonts/lmroman10-italic.otf +0 -0
  41. data/fonts/lmroman10-regular.otf +0 -0
  42. data/lib/rsyntaxtree/error_message.rb +0 -68
  43. data/lib/rsyntaxtree/graph.rb +0 -312
  44. data/lib/rsyntaxtree/tree_graph.rb +0 -327
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ #==========================
5
+ # graph.rb
6
+ #==========================
7
+ #
8
+ # Image utility functions to inspect text font metrics
9
+ # Copyright (c) 2007-2021 Yoichiro Hasebe <yohasebe@gmail.com>
10
+
11
+ require 'utils'
12
+
13
+ module RSyntaxTree
14
+ class BaseGraph
15
+
16
+ def initialize(element_list, params)
17
+
18
+ @element_list = element_list
19
+ @symmetrize = params[:symmetrize]
20
+
21
+ if params[:color]
22
+ @col_node = "blue"
23
+ @col_leaf = "green"
24
+ @col_path = "purple"
25
+ else
26
+ @col_node = "black"
27
+ @col_leaf = "black"
28
+ @col_path = "black"
29
+ end
30
+
31
+ @col_bg = "none"
32
+ @col_fg = "black"
33
+ @col_line = "black"
34
+
35
+ @leafstyle = params[:leafstyle]
36
+ @fontset = params[:fontset]
37
+ @fontsize = params[:fontsize]
38
+ end
39
+
40
+ def calculate_level
41
+ @element_list.get_elements.select{|e| e.type == 2}.each do |e|
42
+ e.level = @element_list.get_id(e.parent).level + 1
43
+ end
44
+ end
45
+
46
+ def calculate_width(id = 1)
47
+ target = @element_list.get_id(id)
48
+ if target.children.empty?
49
+ target.width = target.content_width + $h_gap_between_nodes * 4
50
+
51
+ parent = @element_list.get_id(target.parent)
52
+ while parent && parent.children.size == 1
53
+ w = parent.content_width
54
+ target.width = w + $h_gap_between_nodes * 4 if w > target.content_width
55
+ parent = @element_list.get_id(parent.parent)
56
+ end
57
+ return target.width
58
+ else
59
+ if target.width != 0
60
+ return target.width
61
+ else
62
+ accum_array = []
63
+ target.children.each do |c|
64
+ accum_array << calculate_width(c)
65
+ end
66
+ if @symmetrize
67
+ accum_width = accum_array.max * target.children.size
68
+ else
69
+ accum_width = accum_array.sum
70
+ end
71
+
72
+ target.width = [accum_width, target.content_width].max
73
+ end
74
+
75
+ return target.width
76
+ end
77
+ end
78
+
79
+ def calculate_height(id = 1)
80
+ target = @element_list.get_id(id)
81
+ if id == 1
82
+ target.vertical_indent = 0
83
+ else
84
+ parent = @element_list.get_id(target.parent)
85
+
86
+ if !target.triangle && @leafstyle == "nothing" && ETYPE_LEAF == target.type && parent.children.size == 1
87
+ vertical_indent = parent.vertical_indent + parent.content_height
88
+ else
89
+ vertical_indent = parent.vertical_indent + parent.content_height + $height_connector
90
+ end
91
+
92
+ target.vertical_indent = vertical_indent
93
+ end
94
+
95
+ if target.children.empty?
96
+ target.height = target.content_height
97
+ vertical_end = target.vertical_indent + target.content_height
98
+ else
99
+ accum_array = []
100
+ target.children.each do |c|
101
+ accum_array << calculate_height(c)
102
+ end
103
+ target.height = accum_array.max - target.vertical_indent
104
+ vertical_end = accum_array.max
105
+ end
106
+ return vertical_end
107
+ end
108
+
109
+ def make_balance(id = 1)
110
+ target = @element_list.get_id(id)
111
+ if target.children.empty?
112
+ parent = @element_list.get_id(target.parent)
113
+ accum_array = []
114
+ parent.children.each do |c|
115
+ accum_array << @element_list.get_id(c).width
116
+ end
117
+ max = accum_array.max
118
+ parent.children.each do |c|
119
+ @element_list.get_id(c).width = max
120
+ end
121
+ return max
122
+ else
123
+ accum_array = []
124
+ target.children.each do |c|
125
+ accum_array << make_balance(c)
126
+ end
127
+ accum_width = accum_array.max
128
+ max = [accum_width, target.content_width].max
129
+ target.children.each do |c|
130
+ @element_list.get_id(c).width = max
131
+ end
132
+ return target.width
133
+ end
134
+ end
135
+
136
+ def calculate_indent
137
+ node_groups = @element_list.get_elements.group_by {|e| e.parent}
138
+ node_groups.each do |k, v|
139
+ next if k == 0
140
+ parent = @element_list.get_id(k)
141
+ if @symmetrize
142
+ num_leaves = v.size
143
+ partition_width = parent.width / num_leaves
144
+ left_offset = parent.horizontal_indent + parent.content_width / 2.0 - parent.width / 2.0
145
+ v.each do |e|
146
+ indent = left_offset + (partition_width - e.content_width) / 2.0
147
+ e.horizontal_indent = indent
148
+ left_offset += partition_width
149
+ end
150
+ else
151
+ left_offset = parent.horizontal_indent + parent.content_width / 2.0 - parent.width / 2.0
152
+ v.each do |e|
153
+ indent = left_offset + (e.width - e.content_width) / 2.0
154
+ e.horizontal_indent = indent
155
+ left_offset += e.width
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ def draw_elements
162
+ @element_list.get_elements.each do |element|
163
+ draw_element(element)
164
+ end
165
+ end
166
+
167
+ def draw_connector(id = 1)
168
+ parent = @element_list.get_id(id)
169
+ children = parent.children.map{|c| @element_list.get_id(c)}
170
+
171
+ if children.empty?
172
+ ;
173
+ elsif children.size == 1
174
+ child = children[0]
175
+ if (@leafstyle == "auto")
176
+ if child.contains_phrase || child.triangle
177
+ triangle_to_parent(parent, child)
178
+ else
179
+ line_to_parent(parent, child)
180
+ end
181
+ elsif(@leafstyle == "bar")
182
+ if child.triangle
183
+ triangle_to_parent(parent, child)
184
+ else
185
+ line_to_parent(parent, child)
186
+ end
187
+ elsif(@leafstyle == "nothing")
188
+ if child.triangle
189
+ triangle_to_parent(parent, child)
190
+ elsif ETYPE_LEAF == child.type
191
+ child.vertical_indent = parent.vertical_indent + parent.content_height + $height_connector / 2
192
+ end
193
+ end
194
+ else
195
+ children.each do |child|
196
+ line_to_parent(parent, child)
197
+ end
198
+ end
199
+
200
+ parent.children.each do |c|
201
+ draw_connector(c)
202
+ end
203
+ end
204
+
205
+ def get_leftmost(id = 1)
206
+ target = @element_list.get_id(id)
207
+ target_indent = target.horizontal_indent
208
+ children_indent = target.children.map{|c| get_leftmost(c)}
209
+ (children_indent << target_indent).min
210
+ end
211
+
212
+ def get_rightmost(id = 1)
213
+ target = @element_list.get_id(id)
214
+ target_right_end = target.horizontal_indent + target.content_width
215
+ children_right_end = target.children.map{|c| get_rightmost(c)}
216
+ (children_right_end << target_right_end).max
217
+ end
218
+
219
+ def node_centering
220
+ node_groups = @element_list.get_elements.group_by {|e| e.parent}
221
+ node_groups.sort_by{|k, v| -k}.each do |k, v|
222
+ next if k == 0
223
+ parent = @element_list.get_id(k)
224
+ child_positions =v.map {|child| child.horizontal_indent + child.content_width / 2 }
225
+ parent.horizontal_indent = child_positions.min + (child_positions.max - child_positions.min - parent.content_width) / 2
226
+ end
227
+ end
228
+
229
+ def parse_list
230
+ calculate_level
231
+ calculate_width
232
+ make_balance if @symmetrize
233
+ calculate_indent
234
+ node_centering
235
+
236
+ top = @element_list.get_id(1)
237
+ diff = top.horizontal_indent
238
+ @element_list.get_elements.each do |e|
239
+ e.horizontal_indent -= diff
240
+ end
241
+
242
+ offset_l = (top.horizontal_indent - get_leftmost) + $h_gap_between_nodes
243
+ offset_r = top.width / 2 - offset_l - $h_gap_between_nodes
244
+
245
+ @element_list.get_elements.each do |e|
246
+ e.horizontal_indent += offset_l
247
+ end
248
+
249
+ calculate_height
250
+ draw_elements
251
+ draw_connector
252
+ num_paths = draw_paths
253
+
254
+ # width = get_rightmost - get_leftmost + $h_gap_between_nodes * 2
255
+ width = get_rightmost - get_leftmost + $h_gap_between_nodes
256
+ height = @element_list.get_id(1).height
257
+ height = @height if @height > height
258
+
259
+ return {height: height, width: width}
260
+ end
261
+ end
262
+ end
@@ -6,43 +6,174 @@
6
6
  #==========================
7
7
  #
8
8
  # Aa class that represents a basic tree element, either node or leaf.
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
- class Element
11
+ require "markup_parser"
12
+ require 'utils'
13
+
14
+ module RSyntaxTree
15
+ class Element
17
16
 
18
- attr_accessor :id, :parent, :type, :content, :level, :width, :indent, :triangle
19
- def initialize(id = 0, parent = 0, content = "", level = 0, type = ETYPE_LEAF)
17
+ attr_accessor :id,
18
+ :parent, :type, :level,
19
+ :width, :height,
20
+ :content, :content_width, :content_height,
21
+ :horizontal_indent, :vertical_indent,
22
+ :triangle, :enclosure, :children, :parent,
23
+ :font, :fontsize, :contains_phrase,
24
+ :path
25
+
26
+ def initialize(id, parent, content, level, fontset, fontsize)
27
+ @type = ETYPE_LEAF
20
28
  @id = id # Unique element id
21
29
  @parent = parent # Parent element id
30
+ @children = [] # Child element ids
22
31
  @type = type # Element type
23
32
  @level = level # Element level in the tree (0=top etc...)
24
- @width = 0 # Width of the element in pixels
25
- @indent = 0 # Drawing offset
33
+ @width = 0 # Width of the part of the tree including itself and it governs
34
+ @content_width = 0 # Width of the content
35
+ @horizontal_indent = 0 # Drawing offset
36
+ @vertical_indent = 0 # Drawing offset
26
37
  content = content.strip
27
- if /\A.+\^\z/m =~ content
28
- @content = content.gsub("^"){""} # The actual element content
29
- @triangle = true # draw triangle instead of stright bar when in auto mode
38
+
39
+ if /.+?\^?((?:\+\>?\d+)+)\^?\z/m =~ content
40
+ @path = $1.sub(/\A\+/, "").split("+")
30
41
  else
31
- @content = content.gsub("^"){""}.strip # The actual element content
32
- @triangle = false # draw triangle instead of stright bar when in auto mode
42
+ @path = []
33
43
  end
34
- # workaround to save "[A [B [C] [D] ] [E [F] [G [H] [J] ] ] ]"
44
+
45
+ @fontset = fontset
46
+ @fontsize = fontsize
47
+
48
+ parsed = Markup.parse(content)
49
+
50
+ if parsed[:status] == :success
51
+ results = parsed[:results]
52
+ else
53
+ error_text = "Error: input text contains an invalid string"
54
+ error_text += "\n > " + content
55
+ raise RSTError, error_text
56
+ end
57
+ @content = results[:contents]
58
+ @paths = results[:paths]
59
+ @enclosure = results[:enclosure]
60
+ @triangle = results[:triangle]
61
+
62
+ @contains_phrase = false
63
+ setup
35
64
  end
36
65
 
37
- # Debug helper function
38
- def dump
39
- printf( "ID : %d\n", @id );
40
- printf( "Parent : %d\n", @parent );
41
- printf( "Level : %d\n", @level );
42
- printf( "Type : %d\n", @type );
43
- printf( "Width : %d\n", @width );
44
- printf( "Indent : %d\n", @indent );
45
- printf( "Content : %s\n\n", @content );
66
+ def setup
67
+ total_width = 0
68
+ total_height = 0
69
+ @content.each_with_index do |content, idx|
70
+ content_width = 0
71
+ case content[:type]
72
+ when :border, :bborder
73
+ height = $single_line_height / 2
74
+ content[:height] = height
75
+ total_height += height
76
+ when :text
77
+ row_width = 0
78
+ elements_height = []
79
+ content[:elements].each do |e|
80
+ text = e[:text]
81
+ e[:text] = text.gsub(" ", WHITESPACE_BLOCK).gsub(">", '&#62;').gsub("<", '&#60;')
82
+
83
+
84
+ @contains_phrase = true if text.include?(" ")
85
+ decoration = e[:decoration]
86
+ fontsize = decoration.include?(:small) || decoration.include?(:small) ? @fontsize * SUBSCRIPT_CONST : @fontsize
87
+ fontsize = decoration.include?(:subscript) || decoration.include?(:superscript) ? fontsize * SUBSCRIPT_CONST : fontsize
88
+ style = decoration.include?(:italic) || decoration.include?(:bolditalic) ? :italic : :normal
89
+ weight = decoration.include?(:bold) || decoration.include?(:bolditalic) ? :bold : :normal
90
+
91
+ # e[:cjk] = false
92
+ # if e[:decoration].include?(:math)
93
+ # font = @fontset[:math]
94
+ # elsif text.contains_cjk?
95
+ # font = @fontset[:cjk]
96
+ # e[:cjk] = true
97
+ # elsif decoration.include? :bolditalic
98
+ if decoration.include? :bolditalic
99
+ font = @fontset[:bolditalic]
100
+ elsif decoration.include? :bold
101
+ font = @fontset[:bold]
102
+ elsif decoration.include? :italic
103
+ font = @fontset[:italic]
104
+ else
105
+ font = @fontset[:normal]
106
+ end
107
+
108
+ standard_metrics = FontMetrics.get_metrics('X', @fontset[:normal], fontsize, :normal, :normal)
109
+ height = standard_metrics.height
110
+ if /\A[\<\>]+\z/ =~ text
111
+ width = standard_metrics.width * text.size / 2
112
+ elsif text.contains_emoji?
113
+ segments = text.split_by_emoji
114
+ width = 0
115
+ segments.each do |seg|
116
+ if /\s/ =~ seg[:char]
117
+ ch = 't'
118
+ else
119
+ ch = seg[:char]
120
+ end
121
+ if seg[:type] == :emoji
122
+ this_font = @fontset[:emoji]
123
+ metrics = FontMetrics.get_metrics(ch, this_font, fontsize, style, weight)
124
+ width += metrics.width
125
+ else
126
+ this_font = font
127
+ metrics = FontMetrics.get_metrics(ch, this_font, fontsize, style, weight)
128
+ width += metrics.width
129
+ end
130
+ end
131
+ else
132
+ text.gsub!("\\\\", 'i')
133
+ text.gsub!("\\", "")
134
+ text.gsub!(" ", "x")
135
+ text.gsub!("%", "X")
136
+ metrics = FontMetrics.get_metrics(text, font, fontsize, style, weight)
137
+ width = metrics.width
138
+ end
139
+
140
+ if e[:decoration].include?(:box) || e[:decoration].include?(:circle) || e[:decoration].include?(:bar)
141
+ if e[:text].size == 1
142
+ e[:content_width] = width
143
+ width += (height - width)
144
+ else
145
+ e[:content_width] = width
146
+ width += $width_half_X
147
+ end
148
+ end
149
+
150
+ if e[:decoration].include?(:whitespace)
151
+ width = $width_half_X / 2 * e[:text].size / 4
152
+ e[:text] = ""
153
+ end
154
+
155
+ e[:height] = height
156
+ elements_height << height + $box_vertical_margin / 2
157
+
158
+ e[:width] = width
159
+ row_width += width
160
+ end
161
+
162
+ if @enclosure != :none
163
+ total_height += (elements_height.max + $height_connector_to_text)
164
+ else
165
+ total_height += elements_height.max
166
+ end
167
+ content_width += row_width
168
+ end
169
+ total_width = content_width if total_width < content_width
170
+ end
171
+ @content_width = total_width
172
+ @content_height = total_height
46
173
  end
47
174
 
175
+ def add_child(child_id)
176
+ @children << child_id
177
+ end
178
+ end
48
179
  end
@@ -6,23 +6,25 @@
6
6
  #==========================
7
7
  #
8
8
  # Contains a list of unordered tree elements with a defined parent
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 'element'
17
12
 
18
- class ElementList
13
+ module RSyntaxTree
14
+ class ElementList
19
15
 
20
- attr_accessor :elements, :iterator
16
+ attr_accessor :elements, :iterator
21
17
  def initialize
22
18
  @elements = Array.new # The element array
23
19
  @iterator = -1 # Iterator index (used for get_first / get_next)
24
20
  end
25
21
 
22
+ def set_hierarchy
23
+ @elements.each do |e|
24
+ get_id(e.parent).add_child(e.id) unless e.parent == 0
25
+ end
26
+ end
27
+
26
28
  def add(element)
27
29
  @elements << element
28
30
  if(element.parent != 0)
@@ -41,12 +43,12 @@ class ElementList
41
43
  end
42
44
 
43
45
  def get_next
44
- @iterator += 1
45
- if @elements[@iterator]
46
- return @elements[@iterator]
47
- else
48
- return nil
49
- end
46
+ @iterator += 1
47
+ if @elements[@iterator]
48
+ return @elements[@iterator]
49
+ else
50
+ return nil
51
+ end
50
52
  end
51
53
 
52
54
  def get_id(id)
@@ -118,4 +120,5 @@ class ElementList
118
120
  end
119
121
  return maxlevel + 1;
120
122
  end
123
+ end
121
124
  end