rsyntaxtree 1.14.0 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 965fe8dae50ad6fe7907850239f395fda6a5186654a81d36b89400ebe52108f2
4
- data.tar.gz: 4a183744f4d05b63efcbd1e9aedb6a4bf060654fde9dfc62abaeb20e6a38ac8a
3
+ metadata.gz: 9ccd433053f296c9189c9eceb87f61c91ed815ff6b1be77f1cd2115a73eaf1cd
4
+ data.tar.gz: f769d68efb73c1aafc9dc9af9f0e4e7cbd7a0c741caf253905ebe11dc3c454be
5
5
  SHA512:
6
- metadata.gz: ee4fca1e926ad6886aaca041468f61dc0a596eeffa107c4ee3a82576f2ac7ac53eddcdc0ee586fc5f4408fc809188dba1279eb0ff8083892ef1b598aea830ca1
7
- data.tar.gz: 19173b80e3c2376a9f25d6ea72604bc5774194af7eccfc2b1158dfdf75939ed5d700f654b8122ecf3df99242036686666266ed829710fbe22d94bbea965ae91b
6
+ metadata.gz: 0e6218bc0ddf1f4c49e8fac2c8972cbd3e0eb251fe7087150ea73f63b054fe546483588e582a0f01e52ab26c20fd883ab2c67da07adf1f3120755447a7956945
7
+ data.tar.gz: b0810c0df9ede2cd1c36100ae9a3c055ece2d59261f8278ef8de8b79e07921702c7b8db83fa43f81810184c363b860bae3d9a9c1f45b4a02307f5f8220354fa8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,119 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.0] - 2026-08-28
4
+
5
+ The removals announced in 1.10.0 and 1.12.0, and the fixes that came out of
6
+ looking at what was left. Every figure in the gallery draws exactly as it did.
7
+
8
+ ### Removed
9
+ - JPG and GIF output. JPEG blurs line art and GIF has nothing to offer that
10
+ PNG does not do better, and they were the only reason for the RMagick
11
+ dependency. `-f jpg` and `-f gif` are now refused the way any unknown
12
+ format is. Use `-f png`.
13
+ - The RMagick dependency, and with it ImageMagick. A machine that installs
14
+ this gem no longer needs ImageMagick's headers to build one, and the gem
15
+ now asks for four libraries instead of five.
16
+ - The deprecated aliases, which a major release is the moment to drop:
17
+ `symmetrize` and its `-y` flag (say `tidy: symmetric`), `tidy_spacing`
18
+ (say `hspacing`), the undocumented `tidy_nest`, and `nothing`'s
19
+ undocumented spelling `none` in `leafstyle`. Each is refused by name and
20
+ told what replaced it. An unknown option is passed over in silence, since
21
+ a caller may hand its own parameters through, and a removed one read that
22
+ way would have handed back a different figure without a word.
23
+ - `symmetrize` from the LSIF `meta.source.params` block. The layout scale it
24
+ duplicated, `tidy`, is written there already.
25
+
26
+ ### Fixed
27
+ - A triangle drawn bottom to top pointed the wrong way. The shape spans the
28
+ child's text and points at the parent, and which way that is depends on
29
+ which of the two sits higher — the question the connectors ask and the
30
+ triangle did not. Bottom to top the figure folded through itself: the base
31
+ struck through the leaf and the apex through the node. No gallery figure
32
+ carries one, because every bottom-to-top example is a derivation, which
33
+ draws rules instead of connectors.
34
+ - Every label now keeps the same air above it as below it. The connector
35
+ endpoints were measured from the layout box, whose edges sit unevenly
36
+ around the ink, so every figure carried a little more space under each
37
+ label than over it — visibly once a figure was tilted. The enclosures
38
+ were built with symmetric clearances all along; the plain labels now get
39
+ what the boxes always had. Nearly every gallery figure changes by a few
40
+ pixels of line length; no label, no position and no size moves.
41
+ - A movement rail could reach past the right edge of a left-to-right
42
+ canvas and be cut off at it: the rail grows the canvas as it routes, and
43
+ the width it grew — unlike the height — was then recomputed from the
44
+ elements alone. Four pixels at the default spacing, twenty-four at
45
+ hspacing 0.5.
46
+ - A rule written `---` (or `===` for a double rule) was refused inside a
47
+ `#( … #)` matrix, though it was drawn in every other kind of label. Two
48
+ hyphens and four were accepted there and three were not — a distinction
49
+ nothing could explain. The matrix grammar had no rule for it and the
50
+ matrix renderer dropped every row that was not text.
51
+ - The bracket of a matrix that followed something in the same cell was drawn
52
+ on top of it: `AGR |1| [ … ]`, which is how a shared value is written, put
53
+ the right edge of the tag and the left edge of the bracket on the same
54
+ coordinate exactly. A block now keeps as much room in front of it as the
55
+ bracket keeps inside it.
56
+ - In a left-to-right tree, `hspacing` and `vheight` both moved the width and
57
+ nothing moved the height. The layout replaces the gap that separates
58
+ sisters with one of its own and was not scaling it by `hspacing`, so a
59
+ left-to-right figure could be made wider but never shorter.
60
+ - `storoke-width` in the style of every text element of every SVG. Renderers
61
+ ignored the misspelling, so nothing ever looked wrong.
62
+
63
+ ### Added
64
+ - `shear`: the drawn figure tilts by the given angle (degrees, positive
65
+ leaning the top to the right) and lies on a plane drawn behind it, so the
66
+ lean reads as a surface seen at an angle rather than as a mistake. The
67
+ whole picture shears as one affine piece — layout untouched, region
68
+ shades coming out parallelograms on their own, and nothing able to newly
69
+ touch or cross. The plane is fill without an edge — a region shade is
70
+ bounded because it marks one part of a figure off from the rest, and a
71
+ line round the whole drawing would read as a frame. `shear_plane` turns
72
+ it off or gives it a colour, and a transparent background takes it away
73
+ for you: a clear background is asked for in order to lay the figure over
74
+ something else, and a sheet under it is what would defeat that.
75
+ TikZ refuses a sheared figure the way it refuses a derivation.
76
+ - `vmargin`: the clearance between a label and its connectors, the same
77
+ above and below, measured from the ink its row actually contains — a row
78
+ of capitals pays nothing for descenders nobody wrote, and the ends of a
79
+ row's connectors stay level. From 0.1 to 1.0; at nothing at all a node's
80
+ box stops holding a label that draws its own shape.
81
+ - Three gallery examples, in two new categories: the Indo-European family
82
+ tree (Historical Linguistics), where the region shade groups branches
83
+ rather than marking a c-command domain; type-driven semantic composition
84
+ (Formal Semantics); and a tree of typed feature structures with structure
85
+ sharing (Formal Grammar), which is the figure the matrix rule was missing.
86
+
87
+ ### Changed
88
+ - The documents name every option. Four were missing from the one-page
89
+ reference and one from the README, because the test that reads the
90
+ documents built its roll-call from the tables of listed and numeric
91
+ values — and so never asked after the options that take on or off. It
92
+ reads the roll-call from the defaults now, and the README is one of the
93
+ documents it reads.
94
+ - Three tests were added for the shapes of defect this release fixed: one
95
+ asks every feature in the index to read the same in a label, in a matrix
96
+ and in a matrix inside a matrix; one reads the drawn shapes back out of
97
+ the SVG and asks whether any two of them touch; and one asks a
98
+ left-to-right tree to answer to `hspacing` in its height.
99
+ - The sweep draws two more figures — one with a triangle, one with a
100
+ movement rail — and moves the direction, `vmargin` and `shear` among its
101
+ settings, and asks of every triangle that it have an area. Each of the two
102
+ defects above lived in a figure the sweep did not draw, at a direction it
103
+ did not set: the settings alone could not have found either, and a
104
+ triangle folded flat passes every other check there is.
105
+ - The manuals say what TikZ output does with what it cannot draw: it drops
106
+ it rather than refusing, so a label that is a matrix arrives with its
107
+ cells run together on one line.
108
+
109
+ ### Migration
110
+ - `format: "jpg"` / `"gif"` → `"png"`.
111
+ - `symmetrize: "on"` → `tidy: "symmetric"`; `tidy_spacing: n` → `hspacing: n`;
112
+ `leafstyle: "none"` → `"nothing"`. Each has been the documented spelling
113
+ since 1.12.0.
114
+ - Everything else — every other option, and every piece of notation — is
115
+ unchanged.
116
+
3
117
  ## [1.14.0] - 2026-08-27
4
118
 
5
119
  ### Added
@@ -513,12 +627,40 @@ RMagick dependency.
513
627
 
514
628
  ## [1.3.1] - 2024-10-24
515
629
 
630
+ - Square brackets can be escaped with `\[` and `\]`
516
631
  - Bug fixes and improvements
517
632
 
518
633
  ## [1.3.0] - 2024-08-27
519
634
 
520
635
  - TikZ/forest LaTeX output support
521
636
 
522
- ## Previous versions
523
-
524
- See commit history for earlier changes.
637
+ ## Earlier releases (2018–2023)
638
+
639
+ Before this file was kept, the web interface carried the release notes. Moved
640
+ here so the history has one home.
641
+
642
+ - 2023-09 — Cross-hatch rendering issue fixed
643
+ - 2023-02 — PDF download; improved line edges; `Line width` option;
644
+ `Traditional` colour option; `Hide connectors` option; extra connectors
645
+ between nodes
646
+ - 2023-01 — `Noto Sans Mono` font option
647
+ - 2022-11 — `¥` accepted in place of the backslash for escapes; documentation
648
+ in Japanese
649
+ - 2022-06 — RSyntaxTree on the Web: the UI as a source package, runnable
650
+ locally with Docker
651
+ - 2022-02 — `Polyline connector` option
652
+ - 2022-01 — comprehensive rewrite; path drawing (non-directional, directional,
653
+ bidirectional); more flexible text markup; new markup patterns (newlines,
654
+ brackets, horizontal lines, boxed text); triangles in every connector mode;
655
+ improved error display and image zoom in the UI
656
+ - 2021-12 — subscript `_text_` and superscript `__text__`; `\n` inside
657
+ terminal nodes
658
+ - 2021-11 — label-less terminal nodes (`[A [B] [C]]`); auto bracket close;
659
+ math symbol rendering; better image quality; some decoration symbols altered
660
+ - 2019-11 — SVG download error fixed
661
+ - 2019-04 — SVG colour on/off fixed
662
+ - 2018-06 — whitespace in labels with `<>` (`Modal<>AUX`)
663
+ - 2018-03 — `V spacing` option (then called Connector height); syntax
664
+ highlighting of the input; text styles (italic, bold) and decoration
665
+ (underline, overline, line-through); refined site design
666
+ - 2018-02 — margins around the image; better editor (Ace)
data/README.md CHANGED
@@ -69,7 +69,7 @@ Connect nodes with lines or arrows:
69
69
 
70
70
  ### Multiple Output Formats
71
71
 
72
- Generate trees in PNG, SVG, PDF, or LSIF (JSON) format. (JPG and GIF are deprecated and will be removed in 2.0.)
72
+ Generate trees in PNG, SVG, PDF, TikZ, or LSIF (JSON) format.
73
73
 
74
74
  ## Web Interface
75
75
 
@@ -170,18 +170,25 @@ Usage:
170
170
  | Option | Description | Default |
171
171
  |--------|-------------|---------|
172
172
  | `-o, --outdir` | Output directory | `./` |
173
- | `-f, --format` | Output format: png, svg, pdf, lsif, tikz (jpg and gif are deprecated) | `png` |
173
+ | `-f, --format` | Output format: png, svg, pdf, lsif, tikz | `png` |
174
174
  | `-l, --leafstyle` | Leaf style: auto, triangle, bar, nothing | `auto` |
175
175
  | `-n, --fontstyle` | Font style: sans, serif, cjk, mono | `sans` |
176
176
  | `-s, --fontsize` | Font size: 6-26 | `16` |
177
177
  | `-i, --linewidth` | Line width relative to the font size: 0.5-3.0 (`1` = 5% of the font size) | `1.0` |
178
178
  | `-c, --color` | Color mode: modern, traditional, gray (black text, grey lines), off | `modern` |
179
179
  | `-p, --polyline` | Polyline connectors: on, off | `off` |
180
- | `-d, --direction` | Tree layout direction: ttb, ltr | `ttb` |
180
+ | `--hide-default-connectors` | Draw no parent-to-child lines, leaving only the ones written with `+-n`: on, off | `off` |
181
+ | `-d, --direction` | Tree layout direction: ttb, ltr, btt | `ttb` |
181
182
  | `--tidy` | Layout scale: off, symmetric, low, medium, high | `off` |
182
- | `--hspacing` | Horizontal spacing factor, all layout modes (0.5-3.0) | `1.0` |
183
+ | `--hspacing` | How far apart sisters sit, all layout modes (0.5-3.0) | `1.0` |
184
+ | `-v, --vheight` | How far apart levels sit (0.5-5.0) | `2.0` |
185
+ | `--derivation` | Join each node to its daughters with one rule drawn across them: on, off | `off` |
183
186
  | `--hyphen` | How a hyphen reads: markup (`-underline-`) or literal | `markup` |
184
187
  | `-m, --mirror` | Flip the tree horizontally (RTL convention): on, off | `off` |
188
+ | `--shear` | Tilt the drawn figure, in degrees (-45 to 45, positive leans the top right) | `0` |
189
+ | `--shear-plane` | The plane behind a sheared figure: on, off, or a colour | `on` |
190
+ | `--vmargin` | Clearance between a label and its connectors (0.1-1.0, same above and below) | `0.4` |
191
+ | `-r, --transparent` | Leave the background clear instead of painting it white: on, off | `off` |
185
192
  | `--validate` | Check the input without drawing: a JSON diagnosis on stdout, the verdict in the exit code | |
186
193
  | `--notation` | Print a short reference for the notation | |
187
194
 
@@ -215,10 +222,11 @@ a model writing it does better with the reference in front of it than without.
215
222
  [web interface](https://yohasebe.com/rsyntaxtree) or on your own machine —
216
223
  nothing needs installing. Give the model one of these to read:
217
224
 
218
- - [The brief reference](https://yohasebe.github.io/rsyntaxtree/llms.txt), about
219
- a page, leading with the characters that already mean something.
225
+ - [The reference, on one page](https://yohasebe.github.io/rsyntaxtree/notation.txt):
226
+ the characters that already mean something, then every feature at a line
227
+ each, then the options. About 6 KB.
220
228
  - [Everything in one file](https://yohasebe.github.io/rsyntaxtree/llms-full.txt):
221
- the reference, the manual and all the gallery examples, 78 KB.
229
+ the reference, the manual and all the gallery examples, about 85 KB.
222
230
 
223
231
  **If the model can run a shell**, it can draw and then look at what it drew,
224
232
  which is the part that lets it correct itself:
data/bin/rsyntaxtree CHANGED
@@ -9,7 +9,7 @@ require_relative '../lib/rsyntaxtree'
9
9
  require_relative '../lib/rsyntaxtree/utils'
10
10
  require_relative '../lib/rsyntaxtree/format_converter'
11
11
 
12
- # "png, jpg, gif, pdf, svg, lsif, or tikz" — built once from the library's
12
+ # "png, pdf, svg, lsif, or tikz" — built once from the library's
13
13
  # own list so the three places that name the formats cannot drift apart.
14
14
  FORMAT_LIST_MESSAGE = FORMATS.join(", ").sub(/, ([^,]+)\z/, ', or \1')
15
15
 
@@ -24,14 +24,15 @@ CONFIG_VALIDATORS = {
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" },
25
25
  vheight: ->(v) { v.is_a?(Numeric) && v >= 0.5 && v <= 5.0 ? nil : "must be in the range of 0.5-5.0" },
26
26
  color: ->(v) { OPTION_VALUES[:color].include?(v.to_s) ? nil : "must be modern, traditional, gray, or off" },
27
- symmetrize: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
28
27
  transparent: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
29
28
  polyline: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
30
29
  hide_default_connectors: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
31
30
  mirror: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
31
+ shear: ->(v) { v.is_a?(Numeric) && v >= -45 && v <= 45 ? nil : "must be in the range of -45-45" },
32
+ vmargin: ->(v) { v.is_a?(Numeric) && v >= 0.1 && v <= 1.0 ? nil : "must be in the range of 0.1-1.0" },
33
+ shear_plane: ->(v) { v.to_s =~ /\A(on|off)\z/ || RSyntaxTree::COLOR_NAMES.include?(v.to_s.downcase) || v.to_s =~ /\A#(\h{3}|\h{6})\z/ ? nil : "must be on, off, or a colour" },
32
34
  tidy: ->(v) { OPTION_VALUES[:tidy].include?(v.to_s) ? nil : "must be off, symmetric, low, medium, or high" },
33
35
  hspacing: ->(v) { v.is_a?(Numeric) && v >= 0.5 && v <= 3.0 ? nil : "must be in the range of 0.5-3.0" },
34
- tidy_spacing: ->(v) { v.is_a?(Numeric) && v >= 0.5 && v <= 3.0 ? nil : "must be in the range of 0.5-3.0" },
35
36
  direction: ->(v) { OPTION_VALUES[:direction].include?(v.to_s) ? nil : "must be one of: #{OPTION_VALUES[:direction].join(', ')}" },
36
37
  hyphen: ->(v) { OPTION_VALUES[:hyphen].include?(v.to_s) ? nil : "must be markup or literal" }
37
38
  }.freeze
@@ -114,14 +115,15 @@ opts = Optimist.options do
114
115
  opt :linewidth, "Size: 0.5-3.0", default: 1.0, short: :i
115
116
  opt :vheight, "Connector Height: 0.5-5.0", default: 2.0, short: :v
116
117
  opt :color, "Color text and bars: modern, traditional, gray, or off", default: "modern", short: :c
117
- opt :symmetrize, "DEPRECATED alias of --tidy symmetric: on or off", default: "off", short: :y
118
118
  opt :transparent, "Make background transparent: on or off", default: "off", short: :r
119
119
  opt :polyline, "draw polyline connectors: on or off", default: "off", short: :p
120
120
  opt :hide_default_connectors, "make default connectors transparent: on or off", default: "off", short: :none
121
121
  opt :mirror, "flip the tree horizontally (RTL linguistics convention: first word at the right edge): on or off", default: "off"
122
+ opt :shear, "Tilt the drawn figure, in degrees: -45 to 45, positive leaning the top to the right", default: 0.0, short: :none
123
+ opt :shear_plane, "The plane behind a sheared figure: on, off, a colour name, or a hex colour", default: "on", short: :none
124
+ opt :vmargin, "Clearance between a label and its connectors, 0.1-1.0", default: 0.4, short: :none
122
125
  opt :tidy, "tidy tree layout: off, symmetric (radical symmetrization), low (packed, strict leaf positions), medium (packed; leaves may overlap but never swap order), or high (packed; leaf order kept per row only)", default: "off", short: :none
123
126
  opt :hspacing, "Horizontal spacing factor (counterpart of vheight): 0.5-3.0", default: 1.0, short: :none
124
- opt :tidy_spacing, "DEPRECATED alias of --hspacing: 0.5-3.0", default: 1.0, short: :none
125
127
  opt :direction, "Tree layout direction: ttb (top-to-bottom), ltr (left-to-right) or btt (bottom-to-top, the root last)", default: "ttb", short: :d
126
128
  opt :derivation, "Join each node to its daughters with one rule drawn across them, as a derivation is written, rather than with a line to each: on, off", default: "off", short: :none
127
129
  opt :hyphen, "How a hyphen in a label reads: markup (-underline-) or literal (a hyphen; \-underline\- then underlines)", default: "markup", short: :none
@@ -140,8 +142,10 @@ Optimist.die :leafstyle, "must be auto, triangle, bar, or nothing" unless OPTION
140
142
  Optimist.die :fontstyle, "must be sans, serif, cjk, or mono" unless OPTION_VALUES[:fontstyle].include?(opts[:fontstyle])
141
143
  Optimist.die :fontsize, "must be in the range of 6-26" unless opts[:fontsize] >= 6 && opts[:fontsize] <= 26
142
144
  Optimist.die :linewidth, "must be in the range of 0.5-3.0" unless opts[:linewidth] >= 0.5 && opts[:linewidth] <= 3.0
145
+ Optimist.die :shear, "must be in the range of -45-45" unless opts[:shear] >= -45 && opts[:shear] <= 45
146
+ Optimist.die :vmargin, "must be in the range of 0.1-1.0" unless opts[:vmargin] >= 0.1 && opts[:vmargin] <= 1.0
147
+ Optimist.die :shear_plane, "must be on, off, or a colour" unless /\A(on|off)\z/ =~ opts[:shear_plane] || RSyntaxTree::COLOR_NAMES.include?(opts[:shear_plane].downcase) || /\A#(\h{3}|\h{6})\z/ =~ opts[:shear_plane]
143
148
  Optimist.die :color, "must be either modern, traditional, gray, or off" unless OPTION_VALUES[:color].include?(opts[:color])
144
- Optimist.die :symmetrize, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:symmetrize]
145
149
  Optimist.die :vheight, "must be in the range of 0.5-5.0" if opts[:vheight] < 0.5 || opts[:vheight] > 5.0
146
150
  Optimist.die :transparent, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:transparent]
147
151
  Optimist.die :polyline, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:polyline]
@@ -149,7 +153,6 @@ Optimist.die :hide_default_connectors, "must be either on or off" unless /\A(on|
149
153
  Optimist.die :mirror, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:mirror]
150
154
  Optimist.die :tidy, "must be off, symmetric, low, medium, or high" unless OPTION_VALUES[:tidy].include?(opts[:tidy])
151
155
  Optimist.die :hspacing, "must be in the range of 0.5-3.0" if opts[:hspacing] < 0.5 || opts[:hspacing] > 3.0
152
- Optimist.die :tidy_spacing, "must be in the range of 0.5-3.0" if opts[:tidy_spacing] < 0.5 || opts[:tidy_spacing] > 3.0
153
156
 
154
157
  # Merge config file options with CLI options (CLI takes precedence)
155
158
  string_opts = {}
@@ -170,10 +173,6 @@ opts.each do |key, value|
170
173
  end
171
174
  end
172
175
 
173
- if %w[jpg gif].include?(string_opts[:format])
174
- warn "Warning: #{string_opts[:format].upcase} output is deprecated and will be removed in RSyntaxTree 2.0. Use PNG instead."
175
- end
176
-
177
176
  # These print and exit, so they answer before the input is read. Reading
178
177
  # first meant waiting on a pipe that never closes when stdin is not a
179
178
  # terminal, which is how they are reached from a script.
@@ -14,7 +14,6 @@ module RSyntaxTree
14
14
  def initialize(element_list, params, global)
15
15
  @global = global
16
16
  @element_list = element_list
17
- @symmetrize = params[:symmetrize] == true
18
17
  @direction = params[:direction] || "ttb"
19
18
 
20
19
  case params[:color]
@@ -72,13 +71,10 @@ module RSyntaxTree
72
71
  # "off" | "low" (packing, strict leaf positions) | "medium" (packing
73
72
  # with cross-row tucking as long as no two leaves swap left-right
74
73
  # order) | "high" (free tucking; leaf order kept per row only).
75
- # Legacy inputs upgrade into the scale: tidy_nest: on lifts "low" to
76
- # "high", and the old standalone symmetrize: on lifts "off" to
77
- # "symmetric". When tidy packing is active the symmetric layout is
78
- # meaningless, so packing wins over a legacy symmetrize flag.
74
+ # One scale, and nothing beside it: the standalone symmetrize and
75
+ # tidy_nest flags that used to lift a mode into another were removed
76
+ # in 2.0, so what is asked for is what is drawn.
79
77
  tidy_mode = params[:tidy].to_s
80
- tidy_mode = "high" if tidy_mode == "low" && params[:tidy_nest] == true
81
- tidy_mode = "symmetric" if tidy_mode == "off" && @symmetrize
82
78
  @tidy = %w[low medium high].include?(tidy_mode)
83
79
  # :none — leaf spans may not overlap at all (strict positions)
84
80
  # :ordered — spans may overlap, but leaf centers keep their order
@@ -966,7 +962,13 @@ module RSyntaxTree
966
962
  # is disproportionately large relative to the swapped content dimensions.
967
963
  # Use height_connector_to_text / 2 (= font_height / 4) for tight
968
964
  # vertical packing proportional to the font size.
969
- @global[:h_gap_between_nodes] = @global[:height_connector_to_text] / 2
965
+ #
966
+ # Scaled by hspacing, like the gap it replaces. Without it the two
967
+ # spacing options both pushed the width in this direction — hspacing
968
+ # only through the margins it is left in — and nothing at all decided
969
+ # how far apart siblings sat, so a left-to-right tree could be made
970
+ # wider but never shorter.
971
+ @global[:h_gap_between_nodes] = @global[:height_connector_to_text] / 2 * @global[:hspacing]
970
972
 
971
973
  # In LTR, height_connector becomes horizontal depth between levels.
972
974
  # After content swap, content_height = original content_width (small),
@@ -1090,7 +1092,14 @@ module RSyntaxTree
1090
1092
  max_x = @rule_name_edge if @rule_name_edge.to_f > max_x
1091
1093
  width = max_x + @global[:h_gap_between_nodes]
1092
1094
  height = max_y
1095
+ # A movement path routes outside the tree — below it top-to-bottom,
1096
+ # beyond it to the right left-to-right — and grows @height or @width as
1097
+ # it goes. The height has always been read back here; the width was
1098
+ # computed from the elements alone and then overwrote what the path had
1099
+ # grown, so a left-to-right path could reach past the canvas and be cut
1100
+ # off at its edge.
1093
1101
  height = @height if @height > height
1102
+ width = @width if @width > width
1094
1103
  { height: height, width: width }
1095
1104
  end
1096
1105
  end
@@ -13,7 +13,7 @@ require_relative "color_names"
13
13
 
14
14
  module RSyntaxTree
15
15
  class Element
16
- attr_accessor :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
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
@@ -309,6 +309,10 @@ module RSyntaxTree
309
309
  row_holds_text = content[:elements].any? do |e|
310
310
  (e[:decoration] & [:box, :circle, :bar]).empty? && !e[:text].to_s.strip.empty?
311
311
  end
312
+ # True until something is drawn in the current column. A tabstop
313
+ # opens the next one, so a block that starts a column is at the
314
+ # start of a cell however many columns came before it.
315
+ cell_start = true
312
316
  content[:elements].each do |e|
313
317
  # A nested matrix is measured by the same code one level down, and
314
318
  # reports the size of the block it will occupy in this row: its own
@@ -317,7 +321,18 @@ module RSyntaxTree
317
321
  inner = measure_lines(e[:matrix], nested: true)
318
322
  e[:matrix_width] = inner[:width]
319
323
  e[:matrix_height] = inner[:height]
320
- e[:width] = inner[:width] + matrix_bracket_room * 2
324
+ # A block that opens a cell needs nothing in front of it: the
325
+ # column it starts is already held clear. One that follows
326
+ # something in the same cell — the tag on a shared value, as in
327
+ # AGR |1| [ ... ] — was drawn with its bracket on the ink before
328
+ # it, because a run is spaced for the glyph that comes next and
329
+ # a bracket is not a glyph.
330
+ # As much room in front of the bracket as the bracket keeps
331
+ # inside it, so the two sides of it look alike. Half as much
332
+ # left it looking crowded from the outside.
333
+ e[:matrix_lead] = cell_start ? 0 : matrix_bracket_room
334
+ cell_start = false
335
+ e[:width] = inner[:width] + matrix_bracket_room * 2 + e[:matrix_lead]
321
336
  # Two separate allowances. The block is padded inside its own
322
337
  # brackets, above and below, and that padding is part of the row.
323
338
  # The gap that keeps the block clear of the rows either side is
@@ -335,6 +350,14 @@ module RSyntaxTree
335
350
  next
336
351
  end
337
352
 
353
+ # A tabstop opens the next column; anything else puts ink in the
354
+ # one being filled.
355
+ if e[:decoration].to_a.include?(:tabstop)
356
+ cell_start = true
357
+ elsif !e[:text].to_s.empty?
358
+ cell_start = false
359
+ end
360
+
338
361
  text = e[:text]
339
362
  # Handle escaped square brackets
340
363
  text = text.gsub('\\[', '[')
@@ -291,12 +291,18 @@ module RSyntaxTree
291
291
  connector_height: @params[:vheight],
292
292
  horizontal_spacing: @params[:hspacing] || 1.0,
293
293
  line_width: @params[:linewidth],
294
- # The layout scale the figure was drawn on. symmetrize is kept
295
- # for readers of older files; tidy: "symmetric" supersedes it.
294
+ # The layout scale the figure was drawn on. Files written
295
+ # before 2.0 carry a separate symmetrize flag beside it; the
296
+ # scale is what it meant, and it is all that is written now.
296
297
  tidy: @params[:tidy] || "off",
297
- symmetrize: @params[:symmetrize],
298
298
  mirror: @params[:mirror] == true,
299
299
  direction: @params[:direction] || "ttb",
300
+ # Degrees, positive leaning the top right. The geometry below
301
+ # is the unsheared layout — the shear is applied at render — so
302
+ # a reader who wants the drawn look applies it, and one who
303
+ # wants the layout ignores it.
304
+ shear: (@params[:shear] || 0).to_f,
305
+ vmargin: (@params[:vmargin] || 0.4).to_f,
300
306
  # Which of the two readings of a hyphen the input was parsed
301
307
  # under. The input is recorded verbatim above, and the same
302
308
  # string means different things under the two, so a reader
@@ -5,8 +5,13 @@ require 'parslet'
5
5
  class MarkupParser < Parslet::Parser
6
6
  rule(:cr) { str('\\n') }
7
7
  rule(:eof) { any.absent? }
8
- rule(:border) { match('[^\-]').absent? >> str('-').repeat(3).as(:border) >> (eof | cr) }
9
- rule(:bborder) { match('[^=]').absent? >> str('=').repeat(3).as(:bborder) >> (eof | cr) }
8
+ rule(:border) { rule_line >> (eof | cr) }
9
+ rule(:bborder) { double_rule_line >> (eof | cr) }
10
+ # The rule itself, without what ends the line. A matrix ends its rows on
11
+ # its own closing delimiter as well as on a line break, so it supplies its
12
+ # own terminator; folding one in here would have the two consume it twice.
13
+ rule(:rule_line) { match('[^\-]').absent? >> str('-').repeat(3).as(:border) }
14
+ rule(:double_rule_line) { match('[^=]').absent? >> str('=').repeat(3).as(:bborder) }
10
15
 
11
16
  rule(:brectangle) { str('###') }
12
17
  rule(:rectangle) { str('##') }
@@ -79,7 +84,7 @@ class MarkupParser < Parslet::Parser
79
84
  # bare brackets would be read as tree structure and bare parentheses appear
80
85
  # in labels too often to claim.
81
86
  rule(:matrix) { str('#(') >> matrix_line.repeat(1).as(:matrix) >> str('#)') }
82
- rule(:matrix_line) { matrix_markup.repeat(1).as(:line) >> (cr | str('#)').present?) }
87
+ rule(:matrix_line) { (rule_line | double_rule_line | matrix_markup.repeat(1).as(:line)) >> (cr | str('#)').present?) }
83
88
  rule(:matrix_markup) { (matrix | tabstop | matrix_text | decoration | shape | bstroke) }
84
89
  # Text inside a matrix stops at the closing delimiter as well.
85
90
  rule(:matrix_text) { (escaped | (str('#)').absent? >> non_escaped)).repeat(1).as(:text) }
@@ -13,7 +13,10 @@ figure that was meant.
13
13
  - `<>` is one space and `<3>` is three, everywhere — never angle brackets.
14
14
  Where linguistics wants the brackets themselves, write ⟨ and ⟩ (U+27E8/9):
15
15
  SPR\t⟨<>NP<>⟩, 'hand⟨SUBJ,OBJ⟩'. A label written <NP> is refused with this
16
- advice; a stray <3> simply draws three spaces.
16
+ advice; a stray <3> simply draws three spaces. No text face carries these
17
+ two characters, so whatever the machine falls back to draws them — they
18
+ will not quite match the letters beside them, and how far off they look
19
+ depends on the fonts installed.
17
20
  - A pair of hyphens underlines what stands between them: well-made-word draws
18
21
  "made" underlined. (A lone hyphen is refused with a hint.) Write `\-` for a
19
22
  hyphen — V\-bar, HEAD\-DTR — or set the hyphen option to literal.
@@ -84,7 +87,7 @@ Options (the command line spells them --like-this):
84
87
 
85
88
  | option | what it decides |
86
89
  |------------|-----------------|
87
- | format | png, svg, pdf, gif, jpg, tikz, lsif |
90
+ | format | png, svg, pdf, tikz, lsif |
88
91
  | fontstyle | sans, serif, mono, cjk |
89
92
  | fontsize | 6–26 |
90
93
  | color | modern, traditional, off, gray |
@@ -93,10 +96,20 @@ Options (the command line spells them --like-this):
93
96
  | direction | ttb, ltr, btt |
94
97
  | mirror | flip the finished layout, for RTL scripts |
95
98
  | tidy | off, symmetric, low, medium, high — how tightly subtrees pack |
96
- | hspacing | horizontal spacing factor, 0.5–3.0 (tidy_spacing is its old name) |
97
- | vheight | vertical spacing factor, 0.5–5.0 |
99
+ | hspacing | how far apart sisters sit, 0.5–3.0 |
100
+ | vheight | how far apart levels sit, 0.5–5.0 |
101
+ | polyline | draw each connector as two right angles rather than a slanted line |
102
+ | hide_default_connectors | draw no parent-to-child lines, leaving only the ones written with +-n — how a lattice or a network is drawn |
103
+ | transparent | leave the background clear instead of painting it white |
98
104
  | derivation | one rule across the daughters, categorial-grammar style |
99
105
  | hyphen | markup (hyphens underline) or literal (hyphens are hyphens) |
106
+ | shear | tilt the drawn figure: degrees, −45 to 45, positive leans the top right |
107
+ | shear_plane | the plane behind a sheared figure: on (grey), off, or a colour; never drawn on a transparent background |
108
+ | vmargin | clearance between a label and its connectors, 0.1–1.0, the same above and below |
109
+
110
+ hspacing and vheight are named for the top-to-bottom layout, and follow the
111
+ tree rather than the page: with direction ltr, sisters stack downwards and
112
+ hspacing is what separates them, while vheight runs across.
100
113
 
101
114
  The full manual explains each of these with figures, and every example in the
102
115
  gallery is written out beside the figure it draws:
@@ -1,4 +1,4 @@
1
- RSyntaxTree examples: 76 trees, every one verified to draw.
1
+ RSyntaxTree examples: 80 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;
@@ -8,10 +8,10 @@ here through the same parser that draws it, so each one is accepted.
8
8
  Notation reference: rsyntaxtree --notation
9
9
  Check an input without drawing: rsyntaxtree --validate "[S [NP a] [VP b]]"
10
10
 
11
- What the option names on a settings line mean is not in this file, nor in
12
- the reference, which leaves the options out on purpose. For those, see
13
- `rsyntaxtree --help` or the manual: https://yohasebe.github.io/rsyntaxtree/documentation. Everything at
14
- once, this file and the manual together: https://yohasebe.github.io/rsyntaxtree/llms-full.txt
11
+ What the option names on a settings line mean: one line each at the end
12
+ of the reference (`rsyntaxtree --notation`), the full story in the manual
13
+ at https://yohasebe.github.io/rsyntaxtree/documentation. Everything at once, this file and the manual
14
+ together: https://yohasebe.github.io/rsyntaxtree/llms-full.txt
15
15
 
16
16
 
17
17
  ## 000 — RSyntaxTree basic example
@@ -2521,3 +2521,95 @@ Source: Steedman 2000
2521
2521
  [(S\\NP)/NP hates]]]
2522
2522
  [NP London]]
2523
2523
  ```
2524
+
2525
+ ## 082 — Indo-European language family
2526
+
2527
+ Category: Historical Linguistics
2528
+ Settings: direction=ltr fontstyle=noto-serif hspacing=0.5 polyline=on vheight=5.0
2529
+
2530
+ ```
2531
+ [Proto\-Indo\-European
2532
+ [%@#4a7c59:Italic
2533
+ [Latin
2534
+ [Western [*Spanish*] [*Portuguese*] [*French*]]
2535
+ [Eastern [*Italian*] [*Romanian*]]]]
2536
+ [%@#3d6b9e:Germanic
2537
+ [West [*English*] [*German*] [*Dutch*]]
2538
+ [North [*Swedish*] [*Icelandic*]]]
2539
+ [%@#b5651d:Balto\-Slavic
2540
+ [Slavic [*Russian*] [*Polish*] [*Czech*]]
2541
+ [Baltic [*Lithuanian*] [*Latvian*]]]
2542
+ [%@#8e4585:Indo\-Iranian
2543
+ [Indo\-Aryan [*Hindi*] [*Bengali*]]
2544
+ [Iranian [*Persian*] [*Pashto*]]]
2545
+ [%@#7a6a9b:Hellenic [Ancient<>Greek [*Greek*]]]
2546
+ [%@#2f7d7d:Celtic [*Irish*] [*Welsh*]]]
2547
+ ```
2548
+
2549
+ ## 083 — Type-driven semantic composition
2550
+
2551
+ Category: Formal Semantics
2552
+ Settings: color=none fontstyle=noto-serif leafstyle=nothing
2553
+
2554
+ ```
2555
+ [S\n*t*\n**love**(**m**)(**j**)
2556
+ [DP\n*e*\n**j** [*John*]]
2557
+ [VP\n⟨*e*,*t*⟩\nλ*x*.**love**(**m**)(*x*)
2558
+ [V\n⟨*e*,⟨*e*,*t*⟩⟩\nλ*y*λ*x*.**love**(*y*)(*x*) [*loves*]]
2559
+ [DP\n*e*\n**m** [*Mary*]]]]
2560
+ ```
2561
+
2562
+ ## 084 — Typed feature structures with structure sharing
2563
+
2564
+ Category: Formal Grammar
2565
+ Settings: color=none fontstyle=noto-serif leafstyle=nothing
2566
+
2567
+ ```
2568
+ [#(*phrase*\
2569
+ ---\
2570
+ SYN\t#(HEAD\t|1|\
2571
+ VAL\t#(SPR\t⟨<>⟩#)#)#)
2572
+ [#(*phrase*\
2573
+ ---\
2574
+ SYN\t#(HEAD\t#(*noun*\
2575
+ ---\
2576
+ AGR\t|2|#(*3sing*\
2577
+ ---\
2578
+ PER\t3\
2579
+ NUM\t*sg*#)#)\
2580
+ VAL\t#(SPR\t⟨<>⟩#)#)#) [the<>dog]]
2581
+ [#(*word*\
2582
+ ---\
2583
+ SYN\t#(HEAD\t|1|#(*verb*\
2584
+ ---\
2585
+ AGR\t|2|#)\
2586
+ VAL\t#(SPR\t⟨<>*NP*<>⟩#)#)#) [barks]]]
2587
+ ```
2588
+
2589
+ ## 085 — A tree on a tilted plane
2590
+
2591
+ Category: General
2592
+ Settings: fontstyle=noto-serif shear=20 vheight=1.0
2593
+
2594
+ ```
2595
+ [S
2596
+ [NP
2597
+ [D the]
2598
+ [N man]
2599
+ ]
2600
+ [VP
2601
+ [V put]
2602
+ [NP
2603
+ [D the]
2604
+ [N book]
2605
+ ]
2606
+ [PP
2607
+ [P on]
2608
+ [NP
2609
+ [D the]
2610
+ [N table]
2611
+ ]
2612
+ ]
2613
+ ]
2614
+ ]
2615
+ ```