rsyntaxtree 2.5.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/CITATION.cff +2 -2
- data/bin/rsyntaxtree +3 -3
- data/lib/rsyntaxtree/base_graph.rb +19 -8
- data/lib/rsyntaxtree/json_graph.rb +5 -2
- data/lib/rsyntaxtree/notation_core.md +1 -1
- data/lib/rsyntaxtree/notation_examples.md +57 -2
- data/lib/rsyntaxtree/svg_graph.rb +107 -60
- data/lib/rsyntaxtree/tikz_generator.rb +12 -1
- data/lib/rsyntaxtree/version.rb +1 -1
- data/lib/rsyntaxtree.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eb96318785756ecb47e67a06a809eb96bde5c4f63ceda113c13ba9bb221d032c
|
|
4
|
+
data.tar.gz: 48f083c0cec6539557959b0205e61c503f2aeeb68dea8b06ba3e5faf3b8dba6e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fdd263b44eea3a6106bbdf504fe603d966e505d7f9b92251979fd373b5f50ddb15a686d87a3d9ec1ac6ce46d90a1153cfe79637063dabc3953542d66657d25c0
|
|
7
|
+
data.tar.gz: 37ba04230f1909bf30433f67ea51e283f19b9f45eccee865912eaee7fbb7d272bdd28d340f055d17716f92e02352ad5459cac9b29a8366dfc75e657d2fc1886d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.6.0] - 2026-11-12
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `leafstyle: dashed` (`-l dashed`): the line down to each word dashed, every line between nodes solid
|
|
7
|
+
- Gallery: each example lists its leaf alignment, and its number can be found with the browser's search
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- Two movement paths that meet at one label no longer cross, and run on one level when nothing else is in the way
|
|
11
|
+
|
|
3
12
|
## [2.5.0] - 2026-11-04
|
|
4
13
|
|
|
5
14
|
### Added
|
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: 2.
|
|
15
|
-
date-released: 2026-11-
|
|
14
|
+
version: 2.6.0
|
|
15
|
+
date-released: 2026-11-12
|
|
16
16
|
keywords:
|
|
17
17
|
- linguistics
|
|
18
18
|
- syntax tree
|
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" },
|
|
@@ -110,7 +110,7 @@ opts = Optimist.options do
|
|
|
110
110
|
opt :outdir, "Output directory", default: "./", short: :o
|
|
111
111
|
opt :outfilename, "Output file base name", default: "syntree", short: :u
|
|
112
112
|
opt :format, "Output format: #{FORMAT_LIST_MESSAGE}", default: "png", short: :f
|
|
113
|
-
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
|
|
114
114
|
opt :fontstyle, "Font style: sans, serif, cjk, mono", default: "sans", short: :n
|
|
115
115
|
opt :fontsize, "Size: 6-26", default: 16, short: :s
|
|
116
116
|
opt :linewidth, "Size: 0.5-3.0", default: 1.0, short: :i
|
|
@@ -147,7 +147,7 @@ Optimist.die :direction, "must be one of: #{OPTION_VALUES[:direction].join(', ')
|
|
|
147
147
|
Optimist.die :derivation, "must be one of: #{OPTION_VALUES[:derivation].join(', ')}" unless OPTION_VALUES[:derivation].include?(opts[:derivation])
|
|
148
148
|
Optimist.die :hyphen, "must be markup or literal" unless OPTION_VALUES[:hyphen].include?(opts[:hyphen])
|
|
149
149
|
Optimist.die :format, "must be #{FORMAT_LIST_MESSAGE}" unless FORMATS.include?(opts[:format])
|
|
150
|
-
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])
|
|
151
151
|
Optimist.die :fontstyle, "must be sans, serif, cjk, or mono" unless OPTION_VALUES[:fontstyle].include?(opts[:fontstyle])
|
|
152
152
|
Optimist.die :fontsize, "must be in the range of 6-26" unless opts[:fontsize] >= 6 && opts[:fontsize] <= 26
|
|
153
153
|
Optimist.die :linewidth, "must be in the range of 0.5-3.0" unless opts[:linewidth] >= 0.5 && opts[:linewidth] <= 3.0
|
|
@@ -450,8 +450,10 @@ module RSyntaxTree
|
|
|
450
450
|
end
|
|
451
451
|
|
|
452
452
|
# `through` carries the connector chosen above a run of joints leaf
|
|
453
|
-
# alignment inserted, down that run to the child it reaches
|
|
454
|
-
|
|
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)
|
|
455
457
|
parent = @element_list.get_id(id)
|
|
456
458
|
children = parent.children.map { |c| @element_list.get_id(c) }
|
|
457
459
|
|
|
@@ -466,23 +468,32 @@ module RSyntaxTree
|
|
|
466
468
|
|
|
467
469
|
if through
|
|
468
470
|
child = children.first
|
|
469
|
-
|
|
470
|
-
draw_connector(child.id, (through if child.inserted_joint))
|
|
471
|
+
draw_connector(child.id, (through if child.inserted_joint), dashed + draw_line(parent, child, through, dashed))
|
|
471
472
|
return
|
|
472
473
|
end
|
|
473
474
|
|
|
474
475
|
chosen = children.map do |child|
|
|
475
476
|
joints, target = past_inserted_joints(child)
|
|
476
477
|
connector = connector_for(parent, target, children.size == 1)
|
|
478
|
+
connector = :dashed if connector == :line && @leafstyle == "dashed" && ETYPE_LEAF == target.type
|
|
479
|
+
run = 0
|
|
477
480
|
case connector
|
|
478
481
|
when :triangle then triangle_to_parent(parent, target, joints)
|
|
479
|
-
when :line then
|
|
482
|
+
when :line, :dashed then run = draw_line(parent, child, connector, 0)
|
|
480
483
|
else no_connector(parent, target, joints)
|
|
481
484
|
end
|
|
482
|
-
[child, connector]
|
|
485
|
+
[child, connector, run]
|
|
483
486
|
end
|
|
484
487
|
|
|
485
|
-
chosen.each { |child, connector| draw_connector(child.id, (connector if child.inserted_joint)) }
|
|
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
|
|
486
497
|
end
|
|
487
498
|
|
|
488
499
|
# Leaf alignment puts joints between a node and the child it reaches. They
|
|
@@ -507,7 +518,7 @@ module RSyntaxTree
|
|
|
507
518
|
case @leafstyle
|
|
508
519
|
when "auto"
|
|
509
520
|
forced || child.contains_phrase ? :triangle : :line
|
|
510
|
-
when "bar"
|
|
521
|
+
when "bar", "dashed"
|
|
511
522
|
forced ? :triangle : :line
|
|
512
523
|
when "nothing", "none"
|
|
513
524
|
if forced
|
|
@@ -56,8 +56,11 @@ module RSyntaxTree
|
|
|
56
56
|
@nodes << build_node(element)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
def line_to_parent(parent, child)
|
|
60
|
-
|
|
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
66
|
# A triangle spans from the parent to its child, through any joints leaf
|
|
@@ -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 |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
RSyntaxTree examples:
|
|
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
|
+
```
|
|
@@ -57,7 +57,6 @@ module RSyntaxTree
|
|
|
57
57
|
@polygon_styles = "<polygon style='fill: none; stroke: #{@col_connector}; stroke-width:#{@global[:stroke_normal]}; stroke-linejoin:round;stroke-linecap:round;' points='X1 Y1 X2 Y2 X3 Y3' />\n"
|
|
58
58
|
@text_styles = "<text white-space='pre' alignment-baseline='text-top' style='fill: COLOR; stroke-width: 0; font-size: fontsize' x='X_VALUE' y='Y_VALUE'>CONTENT</text>\n"
|
|
59
59
|
@tree_data = String.new
|
|
60
|
-
@visited_x = {}
|
|
61
60
|
@visited_y = {}
|
|
62
61
|
@global = global
|
|
63
62
|
end
|
|
@@ -387,6 +386,12 @@ module RSyntaxTree
|
|
|
387
386
|
d << " L#{at.call(points.last)}"
|
|
388
387
|
end
|
|
389
388
|
|
|
389
|
+
# The dash every dashed line in the figure is drawn with.
|
|
390
|
+
def dash_array
|
|
391
|
+
dash = @fontsize / 4.0
|
|
392
|
+
"stroke-dasharray='#{dash} #{dash}'"
|
|
393
|
+
end
|
|
394
|
+
|
|
390
395
|
# One stroke rather than three lines meeting at right angles. Drawn this way
|
|
391
396
|
# the dash pattern runs round each turn instead of restarting at it, and an
|
|
392
397
|
# arrowhead is the end of the stroke rather than a marker on whichever line
|
|
@@ -402,8 +407,7 @@ module RSyntaxTree
|
|
|
402
407
|
radius = [radius, leg.call(points[-2], points[-1]) - arrow_length].min if end_arrow
|
|
403
408
|
radius = [radius, 0].max
|
|
404
409
|
|
|
405
|
-
|
|
406
|
-
dasharray = dashed ? "stroke-dasharray='#{dash} #{dash}' " : ""
|
|
410
|
+
dasharray = dashed ? "#{dash_array} " : ""
|
|
407
411
|
markers = +""
|
|
408
412
|
markers << "marker-start='url(#arrowStart)' " if start_arrow
|
|
409
413
|
markers << "marker-end='url(#arrow)' " if end_arrow
|
|
@@ -444,13 +448,20 @@ module RSyntaxTree
|
|
|
444
448
|
|
|
445
449
|
RAIL_SLACK = 1e-6
|
|
446
450
|
|
|
451
|
+
# How far apart two rails stand, and two path ends on one label.
|
|
452
|
+
def rail_spacing
|
|
453
|
+
@global[:h_gap_between_nodes] * 1.25
|
|
454
|
+
end
|
|
455
|
+
|
|
447
456
|
# A rail that would run along one already drawn, over part of the same
|
|
448
|
-
# span, is moved outward past it: two
|
|
449
|
-
# one line, and nothing tells which
|
|
457
|
+
# span or less than one spacing short of it, is moved outward past it: two
|
|
458
|
+
# paths whose rails coincide read as one line, and nothing tells which
|
|
459
|
+
# ends it joins. Rails a spacing or more apart end to end stay on one
|
|
460
|
+
# level, as two paths meeting at a label do once their ends are spread.
|
|
450
461
|
#
|
|
451
462
|
# Each move goes outward and past the rail in the way, so no rail can be in
|
|
452
|
-
# the way twice and there are at most as many moves as rails.
|
|
453
|
-
#
|
|
463
|
+
# the way twice and there are at most as many moves as rails. Distances
|
|
464
|
+
# are compared with a little slack: a rail moved exactly one spacing past
|
|
454
465
|
# another can measure a hair under it, and would be moved again forever.
|
|
455
466
|
def clear_of_rails(at, span, spacing, sign)
|
|
456
467
|
@rails ||= []
|
|
@@ -458,7 +469,7 @@ module RSyntaxTree
|
|
|
458
469
|
close = spacing - RAIL_SLACK
|
|
459
470
|
@rails.size.times do
|
|
460
471
|
blocking = @rails.find do |r|
|
|
461
|
-
r[:lo]
|
|
472
|
+
r[:lo] < hi + close && lo < r[:hi] + close && (r[:at] - at).abs < close
|
|
462
473
|
end
|
|
463
474
|
break unless blocking
|
|
464
475
|
|
|
@@ -482,8 +493,38 @@ module RSyntaxTree
|
|
|
482
493
|
b[:max_y] = [b[:max_y], ys.max].max
|
|
483
494
|
end
|
|
484
495
|
|
|
485
|
-
|
|
486
|
-
|
|
496
|
+
# Path ends that stand in one line across the paths' run (on one label,
|
|
497
|
+
# or on labels straight above one another) are spread apart, each to the
|
|
498
|
+
# side its path heads for: a path going left leaves from the left and one
|
|
499
|
+
# going right from the right, so neither crosses the other's rail. Among
|
|
500
|
+
# ends heading the same way, the one that goes farther takes the outer
|
|
501
|
+
# place, since its rail runs outside. Left to right, the same holds with
|
|
502
|
+
# up and down in place of left and right. Neighbouring ends stand one
|
|
503
|
+
# rail spacing apart, the same distance that keeps two rails apart, or
|
|
504
|
+
# closer where that would take them past the narrowest label they share:
|
|
505
|
+
# an end stays on its label, and so off its neighbours and on the canvas.
|
|
506
|
+
def spread_shared_ends(paths)
|
|
507
|
+
along = @direction == "ltr" ? 1 : 0
|
|
508
|
+
ends = Hash.new { |h, k| h[k] = [] }
|
|
509
|
+
paths.each do |t|
|
|
510
|
+
s = [t[:x1], t[:y1]]
|
|
511
|
+
e = [t[:x2], t[:y2]]
|
|
512
|
+
ends[s[along].round] << [t, :s_offset, s[along], e[along], t[:across1]]
|
|
513
|
+
ends[e[along].round] << [t, :t_offset, e[along], s[along], t[:across2]]
|
|
514
|
+
end
|
|
515
|
+
ends.each_value do |shared|
|
|
516
|
+
gaps = shared.size - 1
|
|
517
|
+
step = gaps.zero? ? 0 : [rail_spacing, shared.map(&:last).min / gaps.to_f].min
|
|
518
|
+
shared.each_with_index
|
|
519
|
+
.sort_by { |(_, _, at, other, _), i| [other <=> at, -other, i] }
|
|
520
|
+
.each_with_index do |((t, key, _, _, _), _), place|
|
|
521
|
+
t[key] = (place - (gaps / 2.0)) * step
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def draw_a_path(s_x, s_y, t_x, t_y, target_arrow = :none, s_offset: 0, t_offset: 0)
|
|
527
|
+
spacing = rail_spacing
|
|
487
528
|
min_bulge = @global[:height_connector_to_text]
|
|
488
529
|
# An end that carries an arrowhead needs a run long enough for the head
|
|
489
530
|
# and a stem before the turn. The rail clears the farther end by the
|
|
@@ -496,14 +537,6 @@ module RSyntaxTree
|
|
|
496
537
|
# arc, and taken from a layout measure so it holds at every font size.
|
|
497
538
|
corner_radius = min_bulge / 2.0
|
|
498
539
|
|
|
499
|
-
# Centered offset for multiple lines at the same endpoint
|
|
500
|
-
s_key = "#{s_x.round}"
|
|
501
|
-
t_key = "#{t_x.round}"
|
|
502
|
-
@visited_x[s_key] = (@visited_x[s_key] || 0) + 1
|
|
503
|
-
@visited_x[t_key] = (@visited_x[t_key] || 0) + 1
|
|
504
|
-
s_offset = ((@visited_x[s_key] - 1) - (@visited_x[s_key] - 1) / 2.0) * spacing
|
|
505
|
-
t_offset = ((@visited_x[t_key] - 1) - (@visited_x[t_key] - 1) / 2.0) * spacing
|
|
506
|
-
|
|
507
540
|
dashed = true if target_arrow == :none
|
|
508
541
|
|
|
509
542
|
if @direction == "ltr"
|
|
@@ -545,8 +578,8 @@ module RSyntaxTree
|
|
|
545
578
|
new_y = [new_y, s_y + (sign * arrow_run)].send(outward) if source_arrow
|
|
546
579
|
new_y = [new_y, t_y + (sign * arrow_run)].send(outward) if target_arrow_drawn
|
|
547
580
|
|
|
548
|
-
new_s_x = s_x
|
|
549
|
-
new_t_x = t_x
|
|
581
|
+
new_s_x = s_x + s_offset
|
|
582
|
+
new_t_x = t_x + t_offset
|
|
550
583
|
new_y = clear_of_rails(new_y, [new_s_x, new_t_x].minmax, spacing, sign)
|
|
551
584
|
|
|
552
585
|
note_path_extent([[new_s_x, s_y], [new_s_x, new_y],
|
|
@@ -960,6 +993,14 @@ module RSyntaxTree
|
|
|
960
993
|
# A movement path leaves a label on the side the words are on: below
|
|
961
994
|
# it top to bottom, above it bottom to top.
|
|
962
995
|
path_y = @direction == "btt" ? element.vertical_indent - @global[:height_connector_to_text] : y1
|
|
996
|
+
# How much of the label there is across the paths' run, for the ends
|
|
997
|
+
# that share it to be spread over: an even amount either side of the
|
|
998
|
+
# anchor, which left to right sits below the middle of the label.
|
|
999
|
+
across = if @direction == "ltr"
|
|
1000
|
+
[element.content_height - (@global[:height_connector_to_text] * 1.5), 0].max
|
|
1001
|
+
else
|
|
1002
|
+
element.content_width
|
|
1003
|
+
end
|
|
963
1004
|
et = element.path
|
|
964
1005
|
et.each do |tr|
|
|
965
1006
|
if /\A-(>|<)?(\d+)\z/ =~ tr
|
|
@@ -974,20 +1015,20 @@ module RSyntaxTree
|
|
|
974
1015
|
elsif /\A(?:>|<)(\d+)\z/ =~ tr
|
|
975
1016
|
tr = $1
|
|
976
1017
|
if path_pool_target[tr]
|
|
977
|
-
path_pool_target[tr] << [x1, path_y]
|
|
1018
|
+
path_pool_target[tr] << [x1, path_y, across]
|
|
978
1019
|
else
|
|
979
|
-
path_pool_target[tr] = [[x1, path_y]]
|
|
1020
|
+
path_pool_target[tr] = [[x1, path_y, across]]
|
|
980
1021
|
end
|
|
981
1022
|
path_flags << tr
|
|
982
1023
|
elsif path_pool_source[tr]
|
|
983
1024
|
if path_pool_other[tr]
|
|
984
|
-
path_pool_other[tr] << [x1, path_y]
|
|
1025
|
+
path_pool_other[tr] << [x1, path_y, across]
|
|
985
1026
|
else
|
|
986
|
-
path_pool_other[tr] = [[x1, path_y]]
|
|
1027
|
+
path_pool_other[tr] = [[x1, path_y, across]]
|
|
987
1028
|
end
|
|
988
1029
|
path_flags << tr
|
|
989
1030
|
else
|
|
990
|
-
path_pool_source[tr] = [x1, path_y]
|
|
1031
|
+
path_pool_source[tr] = [x1, path_y, across]
|
|
991
1032
|
path_flags << tr
|
|
992
1033
|
end
|
|
993
1034
|
raise RSTError.new(+"Error: input text contains a path having more than two ends:\n > #{tr}", code: :path_multiple_ends, hint: "A path or line takes exactly two ends; check the numbers.", retryable: true) if path_flags.tally.any? { |_k, v| v > 2 } || line_flags.tally.any? { |_k, v| v > 2 }
|
|
@@ -1008,11 +1049,11 @@ module RSyntaxTree
|
|
|
1008
1049
|
path_flags.delete(k)
|
|
1009
1050
|
if (targets = path_pool_target[k])
|
|
1010
1051
|
targets.each do |t|
|
|
1011
|
-
paths << { x1: v[0], y1: v[1], x2: t[0], y2: t[1], arrow: :single }
|
|
1052
|
+
paths << { x1: v[0], y1: v[1], x2: t[0], y2: t[1], across1: v[2], across2: t[2], arrow: :single }
|
|
1012
1053
|
end
|
|
1013
1054
|
elsif (others = path_pool_other[k])
|
|
1014
1055
|
others.each do |t|
|
|
1015
|
-
paths << { x1: v[0], y1: v[1], x2: t[0], y2: t[1], arrow: :none }
|
|
1056
|
+
paths << { x1: v[0], y1: v[1], x2: t[0], y2: t[1], across1: v[2], across2: t[2], arrow: :none }
|
|
1016
1057
|
end
|
|
1017
1058
|
end
|
|
1018
1059
|
end
|
|
@@ -1025,7 +1066,7 @@ module RSyntaxTree
|
|
|
1025
1066
|
next if fst.nil?
|
|
1026
1067
|
|
|
1027
1068
|
targets.each do |t|
|
|
1028
|
-
paths << { x1: fst[0], y1: fst[1], x2: t[0], y2: t[1], arrow: :double }
|
|
1069
|
+
paths << { x1: fst[0], y1: fst[1], x2: t[0], y2: t[1], across1: fst[2], across2: t[2], arrow: :double }
|
|
1029
1070
|
end
|
|
1030
1071
|
end
|
|
1031
1072
|
|
|
@@ -1033,8 +1074,9 @@ module RSyntaxTree
|
|
|
1033
1074
|
# than across its ends. Left to right, every rail already clears the
|
|
1034
1075
|
# ones drawn before it, in the order written.
|
|
1035
1076
|
paths = paths.each_with_index.sort_by { |t, i| [(t[:x1] - t[:x2]).abs, i] }.map(&:first) unless @direction == "ltr"
|
|
1077
|
+
spread_shared_ends(paths)
|
|
1036
1078
|
paths.each do |t|
|
|
1037
|
-
draw_a_path(t[:x1], t[:y1], t[:x2], t[:y2], t[:arrow])
|
|
1079
|
+
draw_a_path(t[:x1], t[:y1], t[:x2], t[:y2], t[:arrow], s_offset: t[:s_offset], t_offset: t[:t_offset])
|
|
1038
1080
|
end
|
|
1039
1081
|
|
|
1040
1082
|
line_pool.each do |_k, v|
|
|
@@ -1102,8 +1144,7 @@ module RSyntaxTree
|
|
|
1102
1144
|
else
|
|
1103
1145
|
""
|
|
1104
1146
|
end
|
|
1105
|
-
|
|
1106
|
-
dasharray = dashed ? "stroke-dasharray='#{dash} #{dash}'" : ""
|
|
1147
|
+
dasharray = dashed ? dash_array : ""
|
|
1107
1148
|
swidth = bline ? @global[:stroke_bold] : @global[:stroke_normal]
|
|
1108
1149
|
|
|
1109
1150
|
if s_arrow && t_arrow
|
|
@@ -1168,8 +1209,7 @@ module RSyntaxTree
|
|
|
1168
1209
|
else
|
|
1169
1210
|
""
|
|
1170
1211
|
end
|
|
1171
|
-
|
|
1172
|
-
dasharray = dashed ? "stroke-dasharray='#{dash} #{dash}'" : ""
|
|
1212
|
+
dasharray = dashed ? dash_array : ""
|
|
1173
1213
|
swidth = bline ? @global[:stroke_bold] : @global[:stroke_normal]
|
|
1174
1214
|
|
|
1175
1215
|
"<line x1='#{x1}' y1='#{y1}' x2='#{x2}' y2='#{y2}' style='fill: none; stroke: #{col}; stroke-width:#{swidth}; stroke-linecap:round;' #{dasharray} #{string}/>"
|
|
@@ -1372,8 +1412,12 @@ module RSyntaxTree
|
|
|
1372
1412
|
[child_y, parent_y]
|
|
1373
1413
|
end
|
|
1374
1414
|
|
|
1375
|
-
|
|
1376
|
-
|
|
1415
|
+
# Dashed from +dash_from+ on, when given: a line through the joints leaf
|
|
1416
|
+
# alignment inserted is drawn one piece at a time, and each piece picks up
|
|
1417
|
+
# the dash where the one above it left off, so the run reads as one line.
|
|
1418
|
+
# Returns how long the line drawn is.
|
|
1419
|
+
def line_to_parent(parent, child, dash_from: nil)
|
|
1420
|
+
return 0 if child.horizontal_indent.zero?
|
|
1377
1421
|
|
|
1378
1422
|
if @direction == "ltr"
|
|
1379
1423
|
# LTR: parent's right side → child's left side
|
|
@@ -1391,19 +1435,9 @@ module RSyntaxTree
|
|
|
1391
1435
|
mid_y1 = y1
|
|
1392
1436
|
mid_x2 = mid_x1
|
|
1393
1437
|
mid_y2 = y2
|
|
1394
|
-
|
|
1395
|
-
.sub(/CHIY/, y1.to_s)
|
|
1396
|
-
.sub(/MIDX1/, mid_x1.to_s)
|
|
1397
|
-
.sub(/MIDY1/, mid_y1.to_s)
|
|
1398
|
-
.sub(/MIDX2/, mid_x2.to_s)
|
|
1399
|
-
.sub(/MIDY2/, mid_y2.to_s)
|
|
1400
|
-
.sub(/PARX/, x2.to_s)
|
|
1401
|
-
.sub(/PARY/, y2.to_s)
|
|
1438
|
+
points = [[x1, y1], [mid_x1, mid_y1], [mid_x2, mid_y2], [x2, y2]]
|
|
1402
1439
|
else
|
|
1403
|
-
|
|
1404
|
-
line_data = line_data.sub(/Y1/, y1.to_s)
|
|
1405
|
-
line_data = line_data.sub(/X2/, x2.to_s)
|
|
1406
|
-
@tree_data += line_data.sub(/Y2/, y2.to_s)
|
|
1440
|
+
points = [[x1, y1], [x2, y2]]
|
|
1407
1441
|
end
|
|
1408
1442
|
else
|
|
1409
1443
|
# TTB: parent's bottom → child's top
|
|
@@ -1418,25 +1452,38 @@ module RSyntaxTree
|
|
|
1418
1452
|
mid_x2 = par_x
|
|
1419
1453
|
mid_y2 = mid_y1
|
|
1420
1454
|
|
|
1421
|
-
|
|
1422
|
-
.sub(/CHIY/, chi_y.to_s)
|
|
1423
|
-
.sub(/MIDX1/, mid_x1.to_s)
|
|
1424
|
-
.sub(/MIDY1/, mid_y1.to_s)
|
|
1425
|
-
.sub(/MIDX2/, mid_x2.to_s)
|
|
1426
|
-
.sub(/MIDY2/, mid_y2.to_s)
|
|
1427
|
-
.sub(/PARX/, par_x.to_s)
|
|
1428
|
-
.sub(/PARY/, par_y.to_s)
|
|
1455
|
+
points = [[chi_x, chi_y], [mid_x1, mid_y1], [mid_x2, mid_y2], [par_x, par_y]]
|
|
1429
1456
|
else
|
|
1430
1457
|
x1 = child.horizontal_indent + child.content_width / 2.0
|
|
1431
1458
|
x2 = parent.horizontal_indent + parent.content_width / 2.0
|
|
1432
1459
|
y1, y2 = connector_edges(child, parent)
|
|
1433
|
-
|
|
1434
|
-
line_data = @line_styles.sub(/X1/, x1.to_s)
|
|
1435
|
-
line_data = line_data.sub(/Y1/, y1.to_s)
|
|
1436
|
-
line_data = line_data.sub(/X2/, x2.to_s)
|
|
1437
|
-
@tree_data += line_data.sub(/Y2/, y2.to_s)
|
|
1460
|
+
points = [[x1, y1], [x2, y2]]
|
|
1438
1461
|
end
|
|
1439
1462
|
end
|
|
1463
|
+
emit_connector(points, dash_from)
|
|
1464
|
+
end
|
|
1465
|
+
|
|
1466
|
+
# +points+ run from the child to the parent. A dashed line is laid the
|
|
1467
|
+
# other way, from the parent down, so that its dash carries on into the
|
|
1468
|
+
# piece below it.
|
|
1469
|
+
def emit_connector(points, dash_from)
|
|
1470
|
+
dash = ""
|
|
1471
|
+
if dash_from
|
|
1472
|
+
points = points.reverse
|
|
1473
|
+
dash = " #{dash_array} stroke-dashoffset='#{dash_from.round(3)}'"
|
|
1474
|
+
end
|
|
1475
|
+
if points.size == 2
|
|
1476
|
+
(x1, y1), (x2, y2) = points
|
|
1477
|
+
@tree_data += @line_styles.sub(/X1/, x1.to_s).sub(/Y1/, y1.to_s)
|
|
1478
|
+
.sub(/X2/, x2.to_s).sub(/Y2/, y2.to_s).sub(" />", "#{dash} />")
|
|
1479
|
+
else
|
|
1480
|
+
(chix, chiy), (mx1, my1), (mx2, my2), (parx, pary) = points
|
|
1481
|
+
@tree_data += @polyline_styles.sub(/CHIX/, chix.to_s).sub(/CHIY/, chiy.to_s)
|
|
1482
|
+
.sub(/MIDX1/, mx1.to_s).sub(/MIDY1/, my1.to_s)
|
|
1483
|
+
.sub(/MIDX2/, mx2.to_s).sub(/MIDY2/, my2.to_s)
|
|
1484
|
+
.sub(/PARX/, parx.to_s).sub(/PARY/, pary.to_s).sub(" />", "#{dash} />")
|
|
1485
|
+
end
|
|
1486
|
+
points.each_cons(2).sum { |(ax, ay), (bx, by)| Math.hypot(bx - ax, by - ay) }
|
|
1440
1487
|
end
|
|
1441
1488
|
|
|
1442
1489
|
# Draw a triangle between child/parent elements
|
|
@@ -189,7 +189,7 @@ module RSyntaxTree
|
|
|
189
189
|
|
|
190
190
|
label = extract_label(element)
|
|
191
191
|
escaped_label = escape_latex(label)
|
|
192
|
-
head = "#{escaped_label}#{region_option(element)}"
|
|
192
|
+
head = "#{escaped_label}#{leaf_edge_option(element)}#{region_option(element)}"
|
|
193
193
|
|
|
194
194
|
children = element.children
|
|
195
195
|
indent_str = " " * indent
|
|
@@ -202,6 +202,17 @@ module RSyntaxTree
|
|
|
202
202
|
end
|
|
203
203
|
end
|
|
204
204
|
|
|
205
|
+
# The edge down to a word is dashed when leafstyle asks for it, every
|
|
206
|
+
# piece of it: leaf alignment runs it through joints, and forest gives
|
|
207
|
+
# each joint the edge from the node above. Added to the edge style the
|
|
208
|
+
# tree sets, not in place of it, so the width holds.
|
|
209
|
+
def leaf_edge_option(element)
|
|
210
|
+
return "" unless @params[:leafstyle] == "dashed"
|
|
211
|
+
|
|
212
|
+
element = @element_list.get_id(element.children.first) while element.inserted_joint
|
|
213
|
+
element.type == ETYPE_LEAF ? ", edge+=dashed" : ""
|
|
214
|
+
end
|
|
215
|
+
|
|
205
216
|
# forest node option that paints a semi-transparent plane behind the whole
|
|
206
217
|
# subtree governed by +element+ (region shade). Uses forest's "fit to=tree"
|
|
207
218
|
# on the background layer so the plane sits below the tree. Empty string
|
data/lib/rsyntaxtree/version.rb
CHANGED
data/lib/rsyntaxtree.rb
CHANGED
|
@@ -55,7 +55,7 @@ OPTION_VALUES = {
|
|
|
55
55
|
# an alias until 3.0. It collided with a code-intelligence format of the
|
|
56
56
|
# same initials, and claimed an interchange nobody else had implemented.
|
|
57
57
|
format: FORMATS + ["lsif"],
|
|
58
|
-
leafstyle: %w[auto triangle bar nothing],
|
|
58
|
+
leafstyle: %w[auto triangle bar dashed nothing],
|
|
59
59
|
fontstyle: ["sans", "serif", "cjk", "mono", "noto-sans", "noto-serif", "noto-sans-mono", "cjk zenhei"],
|
|
60
60
|
color: %w[modern traditional gray grey off none on true false],
|
|
61
61
|
tidy: %w[off symmetric low medium high on compact true false],
|