rsyntaxtree 2.4.1 → 2.6.0

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.
data/CITATION.cff CHANGED
@@ -11,8 +11,8 @@ repository-code: "https://github.com/yohasebe/rsyntaxtree"
11
11
  # Concept DOI: always resolves to the latest archived version on Zenodo
12
12
  doi: "10.5281/zenodo.21916150"
13
13
  license: MIT
14
- version: 1.11.0
15
- date-released: 2026-08-20
14
+ version: 2.6.0
15
+ date-released: 2026-11-12
16
16
  keywords:
17
17
  - linguistics
18
18
  - syntax tree
data/README.md CHANGED
@@ -144,7 +144,7 @@ older setup still fails to build `gobject-introspection`, `cairo-gobject` or
144
144
  `gio2`, install those three first with
145
145
  `-- --with-ldflags="-Wl,-undefined,dynamic_lookup"` appended, then install
146
146
  RSyntaxTree; or skip the build entirely with the
147
- [Docker image](https://hub.docker.com/r/yohasebe/rsyntaxtree) or the
147
+ [Docker image](https://github.com/yohasebe/rsyntaxtree_web) or the
148
148
  [web interface](https://yohasebe.com/rsyntaxtree).
149
149
 
150
150
  ## Usage
@@ -172,6 +172,7 @@ Usage:
172
172
  | `-c, --color` | Color mode: modern, traditional, gray (black text, grey lines), off | `modern` |
173
173
  | `-p, --polyline` | Polyline connectors: on, off | `off` |
174
174
  | `--hide-default-connectors` | Draw no parent-to-child lines, leaving only the ones written with `+-n`: on, off | `off` |
175
+ | `--leaf-align` | Line the leaves up on one row: off, words (lower the words), subtrees (lower whole subtrees) | `off` |
175
176
  | `-d, --direction` | Tree layout direction: ttb, ltr, btt | `ttb` |
176
177
  | `--tidy` | Layout scale: off, symmetric, low, medium, high | `off` |
177
178
  | `--hspacing` | How far apart sisters sit, all layout modes (0.5-3.0) | `1.0` |
data/bin/rsyntaxtree CHANGED
@@ -18,7 +18,7 @@ CONFIG_VALIDATORS = {
18
18
  outdir: ->(v) { FileTest.directory?(v) ? nil : "must be an existing directory path" },
19
19
  outfilename: ->(v) { v.is_a?(String) ? nil : "must be a string" },
20
20
  format: ->(v) { FORMATS.include?(v.to_s) || v.to_s == "lsif" ? nil : "must be #{FORMAT_LIST_MESSAGE}" },
21
- leafstyle: ->(v) { OPTION_VALUES[:leafstyle].include?(v.to_s) ? nil : "must be auto, triangle, bar, or nothing" },
21
+ leafstyle: ->(v) { OPTION_VALUES[:leafstyle].include?(v.to_s) ? nil : "must be auto, triangle, bar, dashed, or nothing" },
22
22
  fontstyle: ->(v) { OPTION_VALUES[:fontstyle].include?(v.to_s) ? nil : "must be sans, serif, cjk, or mono" },
23
23
  fontsize: ->(v) { v.is_a?(Integer) && v >= 6 && v <= 26 ? nil : "must be in the range of 6-26" },
24
24
  linewidth: ->(v) { v.is_a?(Numeric) && v >= 0.5 && v <= 3.0 ? nil : "must be in the range of 0.5-3.0" },
@@ -27,6 +27,7 @@ CONFIG_VALIDATORS = {
27
27
  transparent: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
28
28
  polyline: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
29
29
  hide_default_connectors: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
30
+ leaf_align: ->(v) { OPTION_VALUES[:leaf_align].include?(v.to_s) ? nil : "must be off, words, or subtrees" },
30
31
  mirror: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
31
32
  shear: ->(v) { v.is_a?(Numeric) && v >= -45 && v <= 45 ? nil : "must be in the range of -45-45" },
32
33
  vmargin: ->(v) { v.is_a?(Numeric) && v >= 0.1 && v <= 1.0 ? nil : "must be in the range of 0.1-1.0" },
@@ -109,7 +110,7 @@ opts = Optimist.options do
109
110
  opt :outdir, "Output directory", default: "./", short: :o
110
111
  opt :outfilename, "Output file base name", default: "syntree", short: :u
111
112
  opt :format, "Output format: #{FORMAT_LIST_MESSAGE}", default: "png", short: :f
112
- opt :leafstyle, "visual style of tree leaves: auto, triangle, bar, or nothing", default: "auto", short: :l
113
+ opt :leafstyle, "visual style of tree leaves: auto, triangle, bar, dashed, or nothing", default: "auto", short: :l
113
114
  opt :fontstyle, "Font style: sans, serif, cjk, mono", default: "sans", short: :n
114
115
  opt :fontsize, "Size: 6-26", default: 16, short: :s
115
116
  opt :linewidth, "Size: 0.5-3.0", default: 1.0, short: :i
@@ -118,6 +119,7 @@ opts = Optimist.options do
118
119
  opt :transparent, "Make background transparent: on or off", default: "off", short: :r
119
120
  opt :polyline, "draw polyline connectors: on or off", default: "off", short: :p
120
121
  opt :hide_default_connectors, "make default connectors transparent: on or off", default: "off", short: :none
122
+ opt :leaf_align, "line the leaves up on one row: off, words (lower the words), or subtrees (lower whole subtrees)", default: "off", short: :none
121
123
  opt :mirror, "flip the tree horizontally (RTL linguistics convention: first word at the right edge): on or off", default: "off"
122
124
  opt :shear, "Tilt the drawn figure, in degrees: -45 to 45, positive leaning the top to the right", default: 0.0, short: :none
123
125
  opt :shear_plane, "The plane behind a sheared figure: on, off, a colour name, or a hex colour", default: "on", short: :none
@@ -145,7 +147,7 @@ Optimist.die :direction, "must be one of: #{OPTION_VALUES[:direction].join(', ')
145
147
  Optimist.die :derivation, "must be one of: #{OPTION_VALUES[:derivation].join(', ')}" unless OPTION_VALUES[:derivation].include?(opts[:derivation])
146
148
  Optimist.die :hyphen, "must be markup or literal" unless OPTION_VALUES[:hyphen].include?(opts[:hyphen])
147
149
  Optimist.die :format, "must be #{FORMAT_LIST_MESSAGE}" unless FORMATS.include?(opts[:format])
148
- Optimist.die :leafstyle, "must be auto, triangle, bar, or nothing" unless OPTION_VALUES[:leafstyle].include?(opts[:leafstyle])
150
+ Optimist.die :leafstyle, "must be auto, triangle, bar, dashed, or nothing" unless OPTION_VALUES[:leafstyle].include?(opts[:leafstyle])
149
151
  Optimist.die :fontstyle, "must be sans, serif, cjk, or mono" unless OPTION_VALUES[:fontstyle].include?(opts[:fontstyle])
150
152
  Optimist.die :fontsize, "must be in the range of 6-26" unless opts[:fontsize] >= 6 && opts[:fontsize] <= 26
151
153
  Optimist.die :linewidth, "must be in the range of 0.5-3.0" unless opts[:linewidth] >= 0.5 && opts[:linewidth] <= 3.0
@@ -157,6 +159,7 @@ Optimist.die :vheight, "must be in the range of 0.5-5.0" if opts[:vheight] < 0.5
157
159
  Optimist.die :transparent, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:transparent]
158
160
  Optimist.die :polyline, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:polyline]
159
161
  Optimist.die :hide_default_connectors, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:hide_default_connectors]
162
+ Optimist.die :leaf_align, "must be off, words, or subtrees" unless OPTION_VALUES[:leaf_align].include?(opts[:leaf_align])
160
163
  Optimist.die :mirror, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:mirror]
161
164
  Optimist.die :tidy, "must be off, symmetric, low, medium, or high" unless OPTION_VALUES[:tidy].include?(opts[:tidy])
162
165
  Optimist.die :hspacing, "must be in the range of 0.5-3.0" if opts[:hspacing] < 0.5 || opts[:hspacing] > 3.0
@@ -67,6 +67,7 @@ module RSyntaxTree
67
67
  @fontsize = params[:fontsize]
68
68
  @mirror = params[:mirror] == true
69
69
  @derivation = params[:derivation] == true
70
+ @leaf_align = !params[:leaf_align].nil? && params[:leaf_align].to_s != "off"
70
71
  # tidy is one layout scale: "symmetric" (radical symmetrization) |
71
72
  # "off" | "low" (packing, strict leaf positions) | "medium" (packing
72
73
  # with cross-row tucking as long as no two leaves swap left-right
@@ -247,7 +248,43 @@ module RSyntaxTree
247
248
  end
248
249
  target.height = accum_array.max - target.vertical_indent
249
250
  accum_array.max
250
- end.tap { level_derivation_rows if id == 1 && @derivation }
251
+ end.tap do
252
+ level_derivation_rows if id == 1 && @derivation
253
+ level_leaf_rows if id == 1 && @leaf_align && !@derivation
254
+ end
255
+ end
256
+
257
+ # Leaf alignment gives every leaf the same depth, but equal depth is not
258
+ # an equal row: each label is set below its own parent, so a taller parent
259
+ # (a label of two lines) or a shorter drop (a leaf pulled up under
260
+ # leafstyle nothing) leaves the words of one depth at different heights.
261
+ # Drawn left to right it is worse, a level being as wide as the label
262
+ # standing in it. Every depth is therefore given one row: it starts below
263
+ # the tallest label of the row above, by the largest drop any of its
264
+ # elements had, so each connector keeps at least the room it was given.
265
+ # Worked in the swapped coordinates the left-to-right layout runs in,
266
+ # where a column is a row.
267
+ def level_leaf_rows
268
+ elements = @element_list.get_elements
269
+ drop = Hash.new(0.0)
270
+ elements.each do |e|
271
+ next unless e.parent.to_i.positive?
272
+
273
+ parent = @element_list.get_id(e.parent)
274
+ drop[e.level] = [drop[e.level], e.vertical_indent - parent.vertical_indent - parent.content_height].max
275
+ end
276
+ tallest = Hash.new(0.0)
277
+ elements.each { |e| tallest[e.level] = [tallest[e.level], e.content_height].max }
278
+
279
+ top = {}
280
+ y = 0.0
281
+ tallest.keys.sort.each do |level|
282
+ y += drop[level]
283
+ top[level] = y
284
+ y += tallest[level]
285
+ end
286
+
287
+ elements.each { |e| e.vertical_indent = top[e.level] }
251
288
  end
252
289
 
253
290
  # In a derivation every premise is given at the start, so the words stand in
@@ -412,7 +449,11 @@ module RSyntaxTree
412
449
  parent.triangle || (ETYPE_LEAF == child.type && child.triangle)
413
450
  end
414
451
 
415
- def draw_connector(id = 1)
452
+ # `through` carries the connector chosen above a run of joints leaf
453
+ # alignment inserted, down that run to the child it reaches; `dashed`
454
+ # carries how far a dashed line has run so far, so that its dash goes on
455
+ # unbroken through each joint.
456
+ def draw_connector(id = 1, through = nil, dashed = 0)
416
457
  parent = @element_list.get_id(id)
417
458
  children = parent.children.map { |c| @element_list.get_id(c) }
418
459
 
@@ -425,40 +466,74 @@ module RSyntaxTree
425
466
  return
426
467
  end
427
468
 
428
- if children.size == 1
429
- child = children[0]
430
- forced = triangle_asked_for?(parent, child)
431
- case @leafstyle
432
- when "auto"
433
- if forced || child.contains_phrase
434
- triangle_to_parent(parent, child)
435
- else
436
- line_to_parent(parent, child)
437
- end
438
- when "bar"
439
- if forced
440
- triangle_to_parent(parent, child)
441
- else
442
- line_to_parent(parent, child)
443
- end
444
- when "nothing", "none"
445
- if forced
446
- triangle_to_parent(parent, child)
447
- elsif ETYPE_LEAF != child.type
448
- line_to_parent(parent, child)
449
- end
450
- end
451
- else
452
- children.each do |child|
453
- line_to_parent(parent, child)
469
+ if through
470
+ child = children.first
471
+ draw_connector(child.id, (through if child.inserted_joint), dashed + draw_line(parent, child, through, dashed))
472
+ return
473
+ end
474
+
475
+ chosen = children.map do |child|
476
+ joints, target = past_inserted_joints(child)
477
+ connector = connector_for(parent, target, children.size == 1)
478
+ connector = :dashed if connector == :line && @leafstyle == "dashed" && ETYPE_LEAF == target.type
479
+ run = 0
480
+ case connector
481
+ when :triangle then triangle_to_parent(parent, target, joints)
482
+ when :line, :dashed then run = draw_line(parent, child, connector, 0)
483
+ else no_connector(parent, target, joints)
454
484
  end
485
+ [child, connector, run]
486
+ end
487
+
488
+ chosen.each { |child, connector, run| draw_connector(child.id, (connector if child.inserted_joint), run) }
489
+ end
490
+
491
+ # A line, dashed when it is one of the lines leafstyle `dashed` draws down
492
+ # to a word. Returns how long it is, for the piece below to carry on from.
493
+ def draw_line(parent, child, connector, dashed)
494
+ return 0 unless %i[line dashed].include?(connector)
495
+
496
+ line_to_parent(parent, child, dash_from: (dashed if connector == :dashed)).to_f
497
+ end
498
+
499
+ # Leaf alignment puts joints between a node and the child it reaches. They
500
+ # are there to place the child, not to change how it is joined, so the
501
+ # connector is chosen for the node and the child past them and carried
502
+ # through them: a triangle runs from the node down to the words, a line
503
+ # passes through each joint, and where no line is drawn none is drawn at
504
+ # all. Joints written into the input are nodes like any other.
505
+ def past_inserted_joints(child)
506
+ joints = []
507
+ while child.inserted_joint
508
+ joints << child
509
+ child = @element_list.get_id(child.children.first)
455
510
  end
511
+ [joints, child]
512
+ end
456
513
 
457
- parent.children.each do |c|
458
- draw_connector(c)
514
+ def connector_for(parent, child, only_child)
515
+ return :line unless only_child
516
+
517
+ forced = triangle_asked_for?(parent, child)
518
+ case @leafstyle
519
+ when "auto"
520
+ forced || child.contains_phrase ? :triangle : :line
521
+ when "bar", "dashed"
522
+ forced ? :triangle : :line
523
+ when "nothing", "none"
524
+ if forced
525
+ :triangle
526
+ elsif ETYPE_LEAF != child.type
527
+ :line
528
+ else
529
+ :none
530
+ end
459
531
  end
460
532
  end
461
533
 
534
+ # A child joined to its parent by nothing at all: no mark to draw.
535
+ def no_connector(_parent, _child, _joints = []); end
536
+
462
537
  def get_leftmost(id = 1)
463
538
  target = @element_list.get_id(id)
464
539
  target_indent = target.horizontal_indent
@@ -13,7 +13,7 @@ require_relative "color_names"
13
13
 
14
14
  module RSyntaxTree
15
15
  class Element
16
- attr_accessor :ink_top, :ink_bottom, :rule_name, :label_with_rule_name, :id, :parent, :type, :level, :width, :height, :content, :content_width, :text_width, :content_height, :horizontal_indent, :vertical_indent, :triangle, :enclosure, :children, :font, :fontsize, :contains_phrase, :path, :color, :raw_content, :region, :region_color
16
+ attr_accessor :ink_top, :ink_bottom, :rule_name, :label_with_rule_name, :id, :parent, :type, :level, :width, :height, :content, :content_width, :text_width, :content_height, :horizontal_indent, :vertical_indent, :triangle, :enclosure, :children, :font, :fontsize, :contains_phrase, :path, :color, :raw_content, :region, :region_color, :inserted_joint
17
17
 
18
18
  # names_a_rule says the content is a mother's label rather than a leaf's
19
19
  # text. Only a mother has a step under it for a name to sit beside, and a
@@ -99,5 +99,100 @@ module RSyntaxTree
99
99
  end
100
100
  maxlevel + 1
101
101
  end
102
+
103
+ # Add invisible `<>` joints to put every leaf at the deepest leaf level.
104
+ def align_leaves(mode, fontset, fontsize, global)
105
+ leaves = @elements.select { |e| e.type == ETYPE_LEAF && e.children.empty? }
106
+ return if leaves.size < 2
107
+
108
+ next_id = @elements.map(&:id).max + 1
109
+
110
+ if mode == "subtrees"
111
+ root = @elements.find { |element| element.parent.zero? }
112
+ add_high_joints(root, next_id, fontset, fontsize, global) if root
113
+ else
114
+ deepest = leaves.map(&:level).max
115
+
116
+ leaves.each do |leaf|
117
+ joints = deepest - leaf.level
118
+ next unless joints.positive?
119
+
120
+ next_id = insert_empty_joints(leaf, joints, next_id, fontset, fontsize, global)
121
+ end
122
+ end
123
+
124
+ renumber_preorder
125
+ end
126
+
127
+ private
128
+
129
+ def add_high_joints(parent, next_id, fontset, fontsize, global)
130
+ children = parent.children.map { |id| get_id(id) }
131
+ heights = children.map { |child| subtree_height(child) }
132
+ deepest = heights.max
133
+
134
+ children.zip(heights).each do |child, height|
135
+ joints = deepest - height
136
+ if joints.positive?
137
+ next_id = insert_empty_joints(child, joints, next_id, fontset, fontsize, global)
138
+ shift_subtree_levels(child, joints)
139
+ end
140
+ next_id = add_high_joints(child, next_id, fontset, fontsize, global) unless child.children.empty?
141
+ end
142
+ next_id
143
+ end
144
+
145
+ def subtree_height(element)
146
+ return 0 if element.children.empty?
147
+
148
+ 1 + element.children.map { |id| subtree_height(get_id(id)) }.max
149
+ end
150
+
151
+ def insert_empty_joints(child, count, next_id, fontset, fontsize, global)
152
+ parent = get_id(child.parent)
153
+ return next_id unless parent
154
+
155
+ count.times do
156
+ joint = Element.new(next_id, parent.id, WHITESPACE_BLOCK, parent.level + 1, fontset, fontsize, global)
157
+ joint.inserted_joint = true
158
+ next_id += 1
159
+ @elements << joint
160
+ insert_between(parent, child, joint)
161
+ parent = joint
162
+ end
163
+ next_id
164
+ end
165
+
166
+ def shift_subtree_levels(element, count)
167
+ element.level += count
168
+ element.children.each { |id| shift_subtree_levels(get_id(id), count) }
169
+ end
170
+
171
+ def insert_between(parent, child, joint)
172
+ parent.children[parent.children.index(child.id)] = joint.id
173
+ joint.add_child(child.id)
174
+ joint.type = ETYPE_NODE
175
+ child.parent = joint.id
176
+ end
177
+
178
+ def renumber_preorder
179
+ root = @elements.find { |element| element.parent.zero? }
180
+ return unless root
181
+
182
+ ordered = []
183
+ visit = lambda do |element|
184
+ ordered << element
185
+ element.children.each { |id| visit.call(get_id(id)) }
186
+ end
187
+ visit.call(root)
188
+
189
+ ids = ordered.each_with_index.to_h { |element, index| [element.id, index + 1] }
190
+ ordered.each do |element|
191
+ element.parent = ids.fetch(element.parent, 0)
192
+ element.children = element.children.map { |id| ids.fetch(id) }
193
+ element.id = ids.fetch(element.id)
194
+ end
195
+ @elements = ordered
196
+ end
102
197
  end
103
198
  end
@@ -56,62 +56,39 @@ module RSyntaxTree
56
56
  @nodes << build_node(element)
57
57
  end
58
58
 
59
- def line_to_parent(parent, child)
60
- @edges << { from: parent.id, to: child.id, type: "dominance", connector: "line" }
59
+ def line_to_parent(parent, child, dash_from: nil)
60
+ edge = { from: parent.id, to: child.id, type: "dominance", connector: "line" }
61
+ edge[:dashed] = true if dash_from
62
+ @edges << edge
63
+ 0
61
64
  end
62
65
 
63
- def triangle_to_parent(parent, child)
64
- @edges << { from: parent.id, to: child.id, type: "dominance", connector: "triangle" }
66
+ # A triangle spans from the parent to its child, through any joints leaf
67
+ # alignment put between them; each edge it crosses is recorded as part of
68
+ # it, so the joints keep their place in the tree.
69
+ def triangle_to_parent(parent, child, joints = [])
70
+ chain_edges(parent, joints, child, "triangle")
65
71
  end
66
72
 
67
- # Override draw_connector to capture all edges including "none" connector
68
- def draw_connector(id = 1)
69
- parent = @element_list.get_id(id)
70
- children = parent.children.map { |c| @element_list.get_id(c) }
71
-
72
- # A derivation joins a node to its daughters with one rule across all of
73
- # them. Recorded as what it is: this format states that it carries how
74
- # the figure was drawn, so calling these lines would be a false record.
75
- if @derivation && !children.empty?
76
- children.each do |child|
77
- @edges << { from: parent.id, to: child.id, type: "dominance", connector: "rule" }
78
- end
79
- parent.children.each { |c| draw_connector(c) }
80
- return
81
- end
73
+ # Recorded rather than left out: this format carries how the figure was
74
+ # drawn, and a child with no mark under it is part of that.
75
+ def no_connector(parent, child, joints = [])
76
+ chain_edges(parent, joints, child, "none")
77
+ end
82
78
 
83
- if children.size == 1
84
- child = children[0]
85
- forced = triangle_asked_for?(parent, child)
86
- case @leafstyle
87
- when "auto"
88
- if forced || child.contains_phrase
89
- triangle_to_parent(parent, child)
90
- else
91
- line_to_parent(parent, child)
92
- end
93
- when "bar"
94
- if forced
95
- triangle_to_parent(parent, child)
96
- else
97
- line_to_parent(parent, child)
98
- end
99
- when "nothing", "none"
100
- if forced
101
- triangle_to_parent(parent, child)
102
- elsif ETYPE_LEAF != child.type
103
- line_to_parent(parent, child)
104
- else
105
- @edges << { from: parent.id, to: child.id, type: "dominance", connector: "none" }
106
- end
107
- end
108
- else
109
- children.each do |child|
110
- line_to_parent(parent, child)
111
- end
79
+ # A derivation joins a node to its daughters with one rule across all of
80
+ # them. Recorded as what it is: this format states that it carries how
81
+ # the figure was drawn, so calling these lines would be a false record.
82
+ def rule_to_parent(parent, children)
83
+ children.each do |child|
84
+ @edges << { from: parent.id, to: child.id, type: "dominance", connector: "rule" }
112
85
  end
86
+ end
113
87
 
114
- parent.children.each { |c| draw_connector(c) }
88
+ def chain_edges(parent, joints, child, connector)
89
+ ([parent] + joints).zip(joints + [child]).each do |from, to|
90
+ @edges << { from: from.id, to: to.id, type: "dominance", connector: connector }
91
+ end
115
92
  end
116
93
 
117
94
  def draw_paths
@@ -331,6 +308,7 @@ module RSyntaxTree
331
308
  # before 2.0 carry a separate symmetrize flag beside it; the
332
309
  # scale is what it meant, and it is all that is written now.
333
310
  tidy: @params[:tidy] || "off",
311
+ leaf_align: @params[:leaf_align],
334
312
  mirror: @params[:mirror] == true,
335
313
  direction: @params[:direction] || "ttb",
336
314
  # Degrees, positive leaning the top right. The geometry below
@@ -99,7 +99,7 @@ Options (the command line spells them --like-this):
99
99
  | fontsize | 6–26 |
100
100
  | color | modern, traditional, off, gray |
101
101
  | linewidth | every line's weight, 0.5–3.0, as a ratio of the font size |
102
- | leafstyle | auto, bar, nothing — what joins a node to its leaf |
102
+ | leafstyle | auto, bar, dashed, nothing — what joins a node to its leaf |
103
103
  | direction | ttb, ltr, btt |
104
104
  | mirror | flip the finished layout, for RTL scripts |
105
105
  | tidy | off, symmetric, low, medium, high — how tightly subtrees pack |
@@ -107,6 +107,7 @@ Options (the command line spells them --like-this):
107
107
  | vheight | how far apart levels sit, 0.5–5.0 |
108
108
  | polyline | draw each connector as two right angles rather than a slanted line |
109
109
  | hide_default_connectors | draw no parent-to-child lines, leaving only the ones written with +-n — how a lattice or a network is drawn |
110
+ | leaf_align | line the leaves up on one row: off, words (lower the words) or subtrees (lower whole subtrees) |
110
111
  | transparent | leave the background clear instead of painting it white |
111
112
  | derivation | one rule across the daughters, categorial-grammar style |
112
113
  | hyphen | markup (hyphens underline) or literal (hyphens are hyphens) |
@@ -1,4 +1,4 @@
1
- RSyntaxTree examples: 81 trees, every one verified to draw.
1
+ RSyntaxTree examples: 85 trees, every one verified to draw.
2
2
 
3
3
  Each is the input behind a figure in the gallery at https://yohasebe.github.io/rsyntaxtree/examples.
4
4
  The settings line names the options the gallery records for that figure;
@@ -2488,7 +2488,7 @@ own(x,<>y)
2488
2488
  ]
2489
2489
  ```
2490
2490
 
2491
- ## 080 — Terminals levelled with pass-through joints
2491
+ ## 080 — Terminals levelled by hand with pass-through joints
2492
2492
 
2493
2493
  Category: General
2494
2494
  Settings: fontstyle=noto-serif tidy=low vheight=1.0
@@ -2639,3 +2639,58 @@ SYN\t#(HEAD\t|1|#)#)
2639
2639
  ---\
2640
2640
  AGR\t|2|#)#)#) [bark]]]
2641
2641
  ```
2642
+
2643
+ ## 087 — Terminals levelled automatically: whole subtrees drop
2644
+
2645
+ Category: General
2646
+ Settings: fontstyle=noto-serif leaf_align=subtrees polyline=on tidy=low vheight=1.0
2647
+
2648
+ ```
2649
+ [S
2650
+ [NP [D the] [N cat]]
2651
+ [VP
2652
+ [V sat]
2653
+ [PP [P on] [NP [D the] [N mat]]]]]
2654
+ ```
2655
+
2656
+ ## 088 — Wh-movement with the terminals levelled
2657
+
2658
+ Category: Generative Grammar
2659
+ Settings: fontstyle=noto-serif leaf_align=subtrees polyline=on tidy=low vheight=1.0
2660
+
2661
+ ```
2662
+ [CP
2663
+ [DP what+>1]
2664
+ [C' [C did]
2665
+ [TP
2666
+ [DP [D the] [NP [N child]]]
2667
+ [T' [T t]
2668
+ [VP [V find] [DP t+1]]]]]]
2669
+ ```
2670
+
2671
+ ## 089 — A Japanese sentence with its words on one row
2672
+
2673
+ Category: Multilingual
2674
+ Settings: fontstyle=noto-serif leaf_align=words leafstyle=dashed tidy=low vheight=1.0
2675
+
2676
+ ```
2677
+ [S
2678
+ [NP [N 太郎] [P が]]
2679
+ [VP
2680
+ [NP [AdjP [Adj 古い]] [NP [N 本] [P を]]]
2681
+ [V 読んだ]]]
2682
+ ```
2683
+
2684
+ ## 090 — A summarised phrase deep in the tree, with the terminals levelled
2685
+
2686
+ Category: General
2687
+ Settings: fontstyle=noto-serif leaf_align=words leafstyle=dashed tidy=low vheight=1.0
2688
+
2689
+ ```
2690
+ [TP
2691
+ [DP [D the]
2692
+ [NP [N man]
2693
+ [PP [P with] [^DP the hat]]]]
2694
+ [T' [T will]
2695
+ [VP [V give] [DP [D the] [NP [N book]]]]]]
2696
+ ```