rsyntaxtree 1.7.0 → 1.8.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.
Files changed (281) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +35 -0
  3. data/CHANGELOG.md +49 -0
  4. data/CITATION.cff +2 -2
  5. data/Dockerfile +3 -4
  6. data/README.md +14 -2
  7. data/bin/rsyntaxtree +29 -20
  8. data/lib/rsyntaxtree/base_graph.rb +381 -2
  9. data/lib/rsyntaxtree/element.rb +14 -0
  10. data/lib/rsyntaxtree/lsif_graph.rb +12 -2
  11. data/lib/rsyntaxtree/string_parser.rb +1 -4
  12. data/lib/rsyntaxtree/svg_graph.rb +12 -9
  13. data/lib/rsyntaxtree/utils.rb +7 -4
  14. data/lib/rsyntaxtree/version.rb +1 -1
  15. data/lib/rsyntaxtree.rb +53 -50
  16. data/rsyntaxtree.gemspec +6 -2
  17. metadata +3 -266
  18. data/dev/generate_examples.rb +0 -96
  19. data/dev/region_shade_proposal.md +0 -66
  20. data/docs/.gitignore +0 -5
  21. data/docs/404.html +0 -25
  22. data/docs/Gemfile +0 -33
  23. data/docs/_config.yml +0 -77
  24. data/docs/_examples/000.md +0 -28
  25. data/docs/_examples/001.md +0 -26
  26. data/docs/_examples/002.md +0 -35
  27. data/docs/_examples/003.md +0 -29
  28. data/docs/_examples/004.md +0 -29
  29. data/docs/_examples/005.md +0 -26
  30. data/docs/_examples/006.md +0 -29
  31. data/docs/_examples/007.md +0 -30
  32. data/docs/_examples/008.md +0 -32
  33. data/docs/_examples/009.md +0 -28
  34. data/docs/_examples/010.md +0 -52
  35. data/docs/_examples/011.md +0 -41
  36. data/docs/_examples/012.md +0 -44
  37. data/docs/_examples/013.md +0 -66
  38. data/docs/_examples/014.md +0 -51
  39. data/docs/_examples/015.md +0 -34
  40. data/docs/_examples/016.md +0 -86
  41. data/docs/_examples/017.md +0 -26
  42. data/docs/_examples/018.md +0 -32
  43. data/docs/_examples/019.md +0 -63
  44. data/docs/_examples/020.md +0 -43
  45. data/docs/_examples/021.md +0 -46
  46. data/docs/_examples/022.md +0 -61
  47. data/docs/_examples/023.md +0 -26
  48. data/docs/_examples/024.md +0 -26
  49. data/docs/_examples/025.md +0 -90
  50. data/docs/_examples/026.md +0 -30
  51. data/docs/_examples/027.md +0 -57
  52. data/docs/_examples/028.md +0 -23
  53. data/docs/_examples/029.md +0 -59
  54. data/docs/_examples/030.md +0 -36
  55. data/docs/_examples/031.md +0 -23
  56. data/docs/_examples/032.md +0 -30
  57. data/docs/_examples/033.md +0 -27
  58. data/docs/_examples/034.md +0 -29
  59. data/docs/_examples/035.md +0 -37
  60. data/docs/_examples/036.md +0 -20
  61. data/docs/_examples/037.md +0 -21
  62. data/docs/_examples/038.md +0 -33
  63. data/docs/_examples/039.md +0 -17
  64. data/docs/_examples/040.md +0 -63
  65. data/docs/_examples/041.md +0 -35
  66. data/docs/_examples/042.md +0 -22
  67. data/docs/_examples/043.md +0 -40
  68. data/docs/_examples/044.md +0 -16
  69. data/docs/_examples/045.md +0 -48
  70. data/docs/_examples/046.md +0 -440
  71. data/docs/_examples/047.md +0 -21
  72. data/docs/_examples/048.md +0 -71
  73. data/docs/_examples/049.md +0 -49
  74. data/docs/_examples/050.md +0 -96
  75. data/docs/_examples/051.md +0 -95
  76. data/docs/_examples/052.md +0 -58
  77. data/docs/_examples/053.md +0 -34
  78. data/docs/_examples/054.md +0 -22
  79. data/docs/_examples/055.md +0 -24
  80. data/docs/_examples/056.md +0 -25
  81. data/docs/_examples/057.md +0 -34
  82. data/docs/_examples/058.md +0 -47
  83. data/docs/_examples/059.md +0 -54
  84. data/docs/_examples/060.md +0 -72
  85. data/docs/_examples/061.md +0 -97
  86. data/docs/_examples/062.md +0 -96
  87. data/docs/_examples/063.md +0 -53
  88. data/docs/_examples/064.md +0 -25
  89. data/docs/_examples/065.md +0 -22
  90. data/docs/_includes/box_and_circle_table.html +0 -78
  91. data/docs/_includes/escape_char_table.html +0 -29
  92. data/docs/_includes/social_media_links.html +0 -13
  93. data/docs/_layouts/default.html +0 -43
  94. data/docs/assets/css/style.scss +0 -31
  95. data/docs/assets/img/000.png +0 -0
  96. data/docs/assets/img/001.png +0 -0
  97. data/docs/assets/img/002.png +0 -0
  98. data/docs/assets/img/003.png +0 -0
  99. data/docs/assets/img/004.png +0 -0
  100. data/docs/assets/img/005.png +0 -0
  101. data/docs/assets/img/006.png +0 -0
  102. data/docs/assets/img/007.png +0 -0
  103. data/docs/assets/img/008.png +0 -0
  104. data/docs/assets/img/009.png +0 -0
  105. data/docs/assets/img/010.png +0 -0
  106. data/docs/assets/img/011.png +0 -0
  107. data/docs/assets/img/012.png +0 -0
  108. data/docs/assets/img/013.png +0 -0
  109. data/docs/assets/img/014.png +0 -0
  110. data/docs/assets/img/015.png +0 -0
  111. data/docs/assets/img/016.png +0 -0
  112. data/docs/assets/img/017.png +0 -0
  113. data/docs/assets/img/018.png +0 -0
  114. data/docs/assets/img/019.png +0 -0
  115. data/docs/assets/img/020.png +0 -0
  116. data/docs/assets/img/021.png +0 -0
  117. data/docs/assets/img/022.png +0 -0
  118. data/docs/assets/img/023.png +0 -0
  119. data/docs/assets/img/024.png +0 -0
  120. data/docs/assets/img/025.png +0 -0
  121. data/docs/assets/img/026.png +0 -0
  122. data/docs/assets/img/027.png +0 -0
  123. data/docs/assets/img/028.png +0 -0
  124. data/docs/assets/img/029.png +0 -0
  125. data/docs/assets/img/030.png +0 -0
  126. data/docs/assets/img/031.png +0 -0
  127. data/docs/assets/img/032.png +0 -0
  128. data/docs/assets/img/033.png +0 -0
  129. data/docs/assets/img/034.png +0 -0
  130. data/docs/assets/img/035.png +0 -0
  131. data/docs/assets/img/036.png +0 -0
  132. data/docs/assets/img/037.png +0 -0
  133. data/docs/assets/img/038.png +0 -0
  134. data/docs/assets/img/039.png +0 -0
  135. data/docs/assets/img/040.png +0 -0
  136. data/docs/assets/img/041.png +0 -0
  137. data/docs/assets/img/042.png +0 -0
  138. data/docs/assets/img/043.png +0 -0
  139. data/docs/assets/img/044.png +0 -0
  140. data/docs/assets/img/045.png +0 -0
  141. data/docs/assets/img/046.png +0 -0
  142. data/docs/assets/img/047.png +0 -0
  143. data/docs/assets/img/048.png +0 -0
  144. data/docs/assets/img/049.png +0 -0
  145. data/docs/assets/img/050.png +0 -0
  146. data/docs/assets/img/051.png +0 -0
  147. data/docs/assets/img/052.png +0 -0
  148. data/docs/assets/img/053.png +0 -0
  149. data/docs/assets/img/054.png +0 -0
  150. data/docs/assets/img/055.png +0 -0
  151. data/docs/assets/img/056.png +0 -0
  152. data/docs/assets/img/057.png +0 -0
  153. data/docs/assets/img/058.png +0 -0
  154. data/docs/assets/img/059.png +0 -0
  155. data/docs/assets/img/060.png +0 -0
  156. data/docs/assets/img/061.png +0 -0
  157. data/docs/assets/img/062.png +0 -0
  158. data/docs/assets/img/063.png +0 -0
  159. data/docs/assets/img/064.png +0 -0
  160. data/docs/assets/img/065.png +0 -0
  161. data/docs/assets/img/elements/arrow_both.png +0 -0
  162. data/docs/assets/img/elements/arrow_both_bold.png +0 -0
  163. data/docs/assets/img/elements/arrow_left.png +0 -0
  164. data/docs/assets/img/elements/arrow_left_bold.png +0 -0
  165. data/docs/assets/img/elements/arrow_right.png +0 -0
  166. data/docs/assets/img/elements/arrow_right_bold.png +0 -0
  167. data/docs/assets/img/elements/circle.png +0 -0
  168. data/docs/assets/img/elements/circle_abc.png +0 -0
  169. data/docs/assets/img/elements/circle_bold.png +0 -0
  170. data/docs/assets/img/elements/circle_hatched.png +0 -0
  171. data/docs/assets/img/elements/circle_one.png +0 -0
  172. data/docs/assets/img/elements/connector.png +0 -0
  173. data/docs/assets/img/elements/connector_bold.png +0 -0
  174. data/docs/assets/img/elements/square.png +0 -0
  175. data/docs/assets/img/elements/square_abc.png +0 -0
  176. data/docs/assets/img/elements/square_bold.png +0 -0
  177. data/docs/assets/img/elements/square_hatched.png +0 -0
  178. data/docs/assets/img/elements/square_one.png +0 -0
  179. data/docs/assets/img/favicon/apple-touch-icon.png +0 -0
  180. data/docs/assets/img/favicon/favicon-16x16.png +0 -0
  181. data/docs/assets/img/favicon/favicon-32x32.png +0 -0
  182. data/docs/assets/img/favicon/favicon.ico +0 -0
  183. data/docs/assets/svg/000.svg +0 -74
  184. data/docs/assets/svg/001.svg +0 -48
  185. data/docs/assets/svg/002.svg +0 -74
  186. data/docs/assets/svg/003.svg +0 -63
  187. data/docs/assets/svg/004.svg +0 -77
  188. data/docs/assets/svg/005.svg +0 -50
  189. data/docs/assets/svg/006.svg +0 -56
  190. data/docs/assets/svg/007.svg +0 -64
  191. data/docs/assets/svg/008.svg +0 -72
  192. data/docs/assets/svg/009.svg +0 -70
  193. data/docs/assets/svg/010.svg +0 -94
  194. data/docs/assets/svg/011.svg +0 -58
  195. data/docs/assets/svg/012.svg +0 -64
  196. data/docs/assets/svg/013.svg +0 -212
  197. data/docs/assets/svg/014.svg +0 -190
  198. data/docs/assets/svg/015.svg +0 -69
  199. data/docs/assets/svg/016.svg +0 -810
  200. data/docs/assets/svg/017.svg +0 -86
  201. data/docs/assets/svg/018.svg +0 -75
  202. data/docs/assets/svg/019.svg +0 -235
  203. data/docs/assets/svg/020.svg +0 -110
  204. data/docs/assets/svg/021.svg +0 -95
  205. data/docs/assets/svg/022.svg +0 -172
  206. data/docs/assets/svg/023.svg +0 -125
  207. data/docs/assets/svg/024.svg +0 -56
  208. data/docs/assets/svg/025.svg +0 -179
  209. data/docs/assets/svg/026.svg +0 -50
  210. data/docs/assets/svg/027.svg +0 -101
  211. data/docs/assets/svg/028.svg +0 -51
  212. data/docs/assets/svg/029.svg +0 -122
  213. data/docs/assets/svg/030.svg +0 -76
  214. data/docs/assets/svg/031.svg +0 -42
  215. data/docs/assets/svg/032.svg +0 -85
  216. data/docs/assets/svg/033.svg +0 -81
  217. data/docs/assets/svg/034.svg +0 -88
  218. data/docs/assets/svg/035.svg +0 -77
  219. data/docs/assets/svg/036.svg +0 -47
  220. data/docs/assets/svg/037.svg +0 -68
  221. data/docs/assets/svg/038.svg +0 -82
  222. data/docs/assets/svg/039.svg +0 -30
  223. data/docs/assets/svg/040.svg +0 -120
  224. data/docs/assets/svg/041.svg +0 -93
  225. data/docs/assets/svg/042.svg +0 -42
  226. data/docs/assets/svg/043.svg +0 -304
  227. data/docs/assets/svg/044.svg +0 -32
  228. data/docs/assets/svg/045.svg +0 -107
  229. data/docs/assets/svg/046.svg +0 -990
  230. data/docs/assets/svg/047.svg +0 -62
  231. data/docs/assets/svg/048.svg +0 -134
  232. data/docs/assets/svg/049.svg +0 -97
  233. data/docs/assets/svg/050.svg +0 -193
  234. data/docs/assets/svg/051.svg +0 -179
  235. data/docs/assets/svg/052.svg +0 -96
  236. data/docs/assets/svg/053.svg +0 -81
  237. data/docs/assets/svg/054.svg +0 -51
  238. data/docs/assets/svg/055.svg +0 -54
  239. data/docs/assets/svg/056.svg +0 -71
  240. data/docs/assets/svg/057.svg +0 -91
  241. data/docs/assets/svg/058.svg +0 -95
  242. data/docs/assets/svg/059.svg +0 -111
  243. data/docs/assets/svg/060.svg +0 -134
  244. data/docs/assets/svg/061.svg +0 -193
  245. data/docs/assets/svg/062.svg +0 -179
  246. data/docs/assets/svg/063.svg +0 -94
  247. data/docs/assets/svg/064.svg +0 -63
  248. data/docs/assets/svg/065.svg +0 -54
  249. data/docs/documentation.md +0 -303
  250. data/docs/documentation_ja.md +0 -313
  251. data/docs/examples.html +0 -76
  252. data/docs/index.html +0 -2
  253. data/fonts/NotoSans-Bold.ttf +0 -0
  254. data/fonts/NotoSans-BoldItalic.ttf +0 -0
  255. data/fonts/NotoSans-Italic.ttf +0 -0
  256. data/fonts/NotoSans-Regular.ttf +0 -0
  257. data/fonts/NotoSansJP-Bold.otf +0 -0
  258. data/fonts/NotoSansJP-Regular.otf +0 -0
  259. data/fonts/NotoSansMath-Regular.ttf +0 -0
  260. data/fonts/NotoSansMono_SemiCondensed-Bold.ttf +0 -0
  261. data/fonts/NotoSansMono_SemiCondensed-Regular.ttf +0 -0
  262. data/fonts/NotoSerif-Bold.ttf +0 -0
  263. data/fonts/NotoSerif-BoldItalic.ttf +0 -0
  264. data/fonts/NotoSerif-Italic.ttf +0 -0
  265. data/fonts/NotoSerif-Regular.ttf +0 -0
  266. data/fonts/NotoSerifJP-Bold.otf +0 -0
  267. data/fonts/NotoSerifJP-Regular.otf +0 -0
  268. data/fonts/OpenMoji-black-glyf.ttf +0 -0
  269. data/fonts/wqy-zenhei.ttf +0 -0
  270. data/img/rsyntaxtree-web-screenshot.png +0 -0
  271. data/img/rsyntaxtree.png +0 -0
  272. data/img/sample.png +0 -0
  273. data/img/sample.svg +0 -38
  274. data/test/cli_test.rb +0 -262
  275. data/test/example_verify_test.rb +0 -79
  276. data/test/format_converter_test.rb +0 -129
  277. data/test/lsif_test.rb +0 -304
  278. data/test/markup_parser_test.rb +0 -260
  279. data/test/node_styling_test.rb +0 -377
  280. data/test/overlap_test.rb +0 -122
  281. data/test/tikz_test.rb +0 -147
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b011905bb833561ebbc545ce04c4c6cf104290298fc6b2018d09f8c9ef58383
4
- data.tar.gz: 7f35db88fa0d6585f6832d6d6621b527e830c5d7772ddb3282ccf1341b990c16
3
+ metadata.gz: 27e1c2eb4e8e276a63ab92e99b32bd3ef607451df95088f379ff093f6a206001
4
+ data.tar.gz: 5147922ca1269a629761d7b93f364f9ae9d22fc41d63ce28fdd4c75223cccc52
5
5
  SHA512:
6
- metadata.gz: 7d96fe35d241eb21bb3248dd9b566a5dd0603bbed8f7118579f915ef9eecb3d875f10508dbdb4ec09f8fffa53ec2a86cb7d9d3d7458dba86f799ff1505f70fd0
7
- data.tar.gz: 9e2298a797787e12407e57d0dc05e59cbb444b6b0792c6be1447190ef99fae4899a2ead1df2d40e80f26615d268ce0e3287029db002c02c1344aceed2a5d7bf5
6
+ metadata.gz: 36edfb6922b94b9e8c0821e72f9154b58ad58fc43bd7f55ae913807534f39acd9dbe8becd00e9f14c30c999bb90cc15b6d7ba9cf38e75a6e1feaeca9c090edb9
7
+ data.tar.gz: 58f60c25f6c8ab2566ff1755c74c353769ea80a970e020f3e2c656bb2b6ee7d8e75fe82831b5d10c3788f8539d601a6817a257c5f57006d492ca11526c30e77b
@@ -0,0 +1,35 @@
1
+ name: Test
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ test:
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ matrix:
15
+ ruby-version: ["3.2", "3.4"]
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install system dependencies
20
+ run: |
21
+ sudo apt-get update
22
+ sudo apt-get install -y \
23
+ libpango1.0-dev librsvg2-dev libmagickwand-dev \
24
+ libgirepository1.0-dev gobject-introspection \
25
+ fonts-noto-core fonts-noto-cjk
26
+ sudo fc-cache -f
27
+
28
+ - name: Set up Ruby ${{ matrix.ruby-version }}
29
+ uses: ruby/setup-ruby@v1
30
+ with:
31
+ ruby-version: ${{ matrix.ruby-version }}
32
+ bundler-cache: true
33
+
34
+ - name: Run tests
35
+ run: bundle exec rake test
data/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.8.0] - 2026-08
4
+
5
+ ### Added
6
+ - `tidy` layout scale, one option covering every layout mode from the most
7
+ spacious to the most dense: `symmetric` (radical symmetrization) | `off` |
8
+ `low` (contour packing, strict leaf positions) | `medium` (packing with
9
+ cross-row tucking that never lets two leaves swap their left-right order) |
10
+ `high` (free tucking; leaf order kept per row only). Connector heights
11
+ adjust automatically: a small height budget (5% of the tree's height) is
12
+ spent on the levels whose branches spread widest, evening out branch
13
+ angles. Tidy never produces overlapping labels (collisions roll back).
14
+ - `mirror` option: flips the finished layout horizontally for the
15
+ right-to-left tree convention of Arabic/Hebrew syntax (composes with
16
+ `direction`).
17
+ - `hspacing` option: scales every horizontal gap in every layout mode — the
18
+ horizontal counterpart of `vheight`/connector height.
19
+ - Pass-through empty nodes: a node labeled only `<>` renders as an invisible
20
+ joint with the connector running continuously through it, so a `<>` chain
21
+ aligns a shallow leaf with deeper leaves without a broken line.
22
+ - Full CJK coverage in every font style: the family chains fall back to
23
+ Noto Sans/Serif/Mono CJK, so Hangul and simplified/traditional Han render
24
+ in all styles (the standalone Noto JP faces carry no Hangul).
25
+ - Example gallery: Multilingual category (the same UD-PUD sentence in nine
26
+ languages, constituency derived mechanically from the dependency
27
+ annotation; the Arabic example demonstrates `mirror`), a Morphology
28
+ category, and per-figure tidy settings across the whole gallery.
29
+ - CI on GitHub Actions (Ruby 3.2/3.4).
30
+
31
+ ### Changed
32
+ - LSIF output records the layout settings a reader cannot recover from the
33
+ coordinates: `geometry.mirror`, and `tidy`, `mirror`, `direction` and
34
+ `horizontal_spacing` under `meta.source.params`.
35
+ - The standalone `symmetrize` option and `-y` flag are deprecated aliases of
36
+ `tidy: symmetric`; `tidy_spacing` is a deprecated alias of `hspacing`.
37
+ - `--direction` now owns the `-d` short flag (it had been auto-assigned to
38
+ `--hide-default-connectors`, so the documented `-d ltr` silently did
39
+ nothing). Every published short flag is now declared explicitly rather
40
+ than derived from the option set. One undocumented auto-assignment moved
41
+ as a result: `-e` was `--direction` in 1.7.0 and is `--version` here.
42
+ - The gem no longer bundles font files (38MB that were never opened at
43
+ runtime since the Pango migration): fonts resolve by family name through
44
+ fontconfig. See README for the system font packages. The dead `--font`
45
+ CLI option (its value was never read) is gone.
46
+
47
+ ### Fixed
48
+ - Boolean options left at their defaults are no longer misread as enabled
49
+ when the caller passes only a partial parameter set (e.g. `tidy: off`
50
+ rendered as `symmetric` in the web UI).
51
+
3
52
  ## [1.7.0] - 2026-08
4
53
 
5
54
  ### Changed
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.7.0
15
- date-released: "2026-08-13"
14
+ version: 1.8.0
15
+ date-released: 2026-08-15
16
16
  keywords:
17
17
  - linguistics
18
18
  - syntax tree
data/Dockerfile CHANGED
@@ -7,16 +7,15 @@ RUN apk update && \
7
7
  apk add --no-cache linux-headers libxml2-dev make gcc libc-dev bash && \
8
8
  apk add --no-cache librsvg librsvg-dev pango-dev imagemagick imagemagick-dev xz-dev libbz2 && \
9
9
  apk add --no-cache gobject-introspection gobject-introspection-dev && \
10
- apk add --no-cache -t .build-packages --no-cache build-base curl-dev wget gcompat
10
+ apk add --no-cache -t .build-packages --no-cache build-base curl-dev wget gcompat && \
11
+ apk add --no-cache font-noto font-noto-cjk font-noto-cjk-extra font-noto-emoji \
12
+ font-noto-arabic font-noto-devanagari font-noto-thai
11
13
 
12
14
 
13
15
  ADD Gemfile $WORKSPACE
14
16
  ADD rsyntaxtree.gemspec $WORKSPACE
15
17
  RUN bundle install -j4
16
18
 
17
- ADD fonts $WORKSPACE
18
- RUN mkdir -p /usr/share/fonts/yh
19
- COPY ./fonts/* /usr/share/fonts/yh/
20
19
  RUN fc-cache -fv
21
20
 
22
21
  ADD . $WORKSPACE
data/README.md CHANGED
@@ -110,6 +110,16 @@ See [RSyntaxTree Example Gallery](https://yohasebe.github.io/rsyntaxtree/example
110
110
 
111
111
  <img src='https://github.com/yohasebe/rsyntaxtree/blob/master/img/sample.png?raw=true' width='600' />
112
112
 
113
+ ## System Fonts
114
+
115
+ RSyntaxTree resolves fonts by family name through fontconfig (measurement via Pango, rendering via librsvg), so the fonts must be installed on the system that generates the images. Recommended:
116
+
117
+ - Debian/Ubuntu: `apt install fonts-noto-core fonts-noto-cjk fonts-noto-color-emoji`
118
+ - Alpine: `apk add font-noto font-noto-cjk font-noto-cjk-extra font-noto-emoji` (the `-extra` package carries Noto Serif CJK)
119
+ - macOS: install [Noto Sans/Serif](https://fonts.google.com/noto) (and Noto Sans/Serif JP for CJK); otherwise the system falls back to available fonts
120
+
121
+ Any script supported by an installed font renders correctly — the family chains fall back from Noto Sans/Serif to the JP and CJK variants, so Japanese, Chinese (simplified and traditional) and Korean are covered by the Noto CJK package. As of v1.8.0 the gem no longer bundles font files (they were not used at runtime).
122
+
113
123
  ## Installation
114
124
 
115
125
  ```bash
@@ -156,9 +166,11 @@ Usage:
156
166
  | `-n, --fontstyle` | Font style: sans, serif, cjk, mono | `sans` |
157
167
  | `-s, --fontsize` | Font size: 8-26 | `16` |
158
168
  | `-c, --color` | Color mode: modern, traditional, off | `modern` |
159
- | `-y, --symmetrize` | Symmetrical tree: on, off | `off` |
160
169
  | `-p, --polyline` | Polyline connectors: on, off | `off` |
161
170
  | `-d, --direction` | Tree layout direction: ttb, ltr | `ttb` |
171
+ | `--tidy` | Layout scale: off, symmetric, low, medium, high | `off` |
172
+ | `--hspacing` | Horizontal spacing factor, all layout modes (0.5-3.0) | `1.0` |
173
+ | `-m, --mirror` | Flip the tree horizontally (RTL convention): on, off | `off` |
162
174
 
163
175
  Run `rsyntaxtree -h` for the full list of options.
164
176
 
@@ -201,7 +213,7 @@ If you use RSyntaxTree in your research, please cite it. You can use the "Cite t
201
213
  title = {RSyntaxTree: A graphical syntax tree image generator},
202
214
  url = {https://yohasebe.com/rsyntaxtree},
203
215
  doi = {10.5281/zenodo.21916150},
204
- version = {1.7.0},
216
+ version = {1.8.0},
205
217
  year = {2026}
206
218
  }
207
219
  ```
data/bin/rsyntaxtree CHANGED
@@ -15,7 +15,6 @@ CONFIG_VALIDATORS = {
15
15
  format: ->(v) { /\A(png|jpg|gif|pdf|svg|lsif)\z/ =~ v.to_s ? nil : "must be png, jpg, gif, pdf, svg, or lsif" },
16
16
  leafstyle: ->(v) { /\A(auto|triangle|bar|nothing)\z/ =~ v.to_s ? nil : "must be auto, triangle, bar, or nothing" },
17
17
  fontstyle: ->(v) { /\A(sans|serif|cjk|mono)\z/ =~ v.to_s ? nil : "must be sans, serif, cjk, or mono" },
18
- font: ->(v) { v.nil? || File.exist?(v) ? nil : "must be path to an existing ttf font" },
19
18
  fontsize: ->(v) { v.is_a?(Integer) && v >= 8 && v <= 26 ? nil : "must be in the range of 8-26" },
20
19
  linewidth: ->(v) { v.is_a?(Integer) && v >= 1 && v <= 5 ? nil : "must be in the range of 1-5" },
21
20
  vheight: ->(v) { v.is_a?(Numeric) && v >= 0.5 && v <= 5.0 ? nil : "must be in the range of 0.5-5.0" },
@@ -24,6 +23,10 @@ CONFIG_VALIDATORS = {
24
23
  transparent: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
25
24
  polyline: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
26
25
  hide_default_connectors: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
26
+ mirror: ->(v) { /\A(on|off|true|false)\z/ =~ v.to_s ? nil : "must be on or off" },
27
+ tidy: ->(v) { /\A(off|symmetric|low|medium|high|on|compact|true|false)\z/ =~ v.to_s ? nil : "must be off, symmetric, low, medium, or high" },
28
+ hspacing: ->(v) { v.is_a?(Numeric) && v >= 0.5 && v <= 3.0 ? nil : "must be in the range of 0.5-3.0" },
29
+ tidy_spacing: ->(v) { v.is_a?(Numeric) && v >= 0.5 && v <= 3.0 ? nil : "must be in the range of 0.5-3.0" },
27
30
  direction: ->(v) { /\A(ttb|ltr)\z/ =~ v.to_s ? nil : "must be ttb or ltr" }
28
31
  }.freeze
29
32
 
@@ -84,7 +87,7 @@ unless loaded[:errors].empty?
84
87
  end
85
88
 
86
89
  opts = Optimist.options do
87
- version "RSyntaxTree #{RSyntaxTree::VERSION} (c) 2024 Yoichiro Hasebe"
90
+ version "RSyntaxTree #{RSyntaxTree::VERSION} (c) 2026 Yoichiro Hasebe"
88
91
  banner <<~BANNER
89
92
  RSyntaxTree, (linguistic) syntax tree generator written in Ruby.
90
93
 
@@ -96,21 +99,24 @@ opts = Optimist.options do
96
99
  where [options] are:
97
100
  BANNER
98
101
 
99
- opt :outdir, "Output directory", default: "./"
100
- opt :outfilename, "Output file base name", default: "syntree"
101
- opt :format, "Output format: png, jpg, gif, pdf, svg, or lsif", default: "png"
102
- opt :leafstyle, "visual style of tree leaves: auto, triangle, bar, or nothing", default: "auto"
103
- opt :fontstyle, "Font style (available when ttf font is specified): sans, serif, cjk, mono", default: "sans"
104
- opt :font, "Path to a ttf font used to generate tree (optional)", type: String
105
- opt :fontsize, "Size: 8-26", default: 16
106
- opt :linewidth, "Size: 1-5", default: 1
107
- opt :vheight, "Connector Height: 0.5-5.0", default: 2.0
108
- opt :color, "Color text and bars: modern, traditional, or off", default: "modern"
109
- opt :symmetrize, "Generate radically symmetrical, balanced tree: on or off", default: "off"
110
- opt :transparent, "Make background transparent: on or off", default: "off"
111
- opt :polyline, "draw polyline connectors: on or off", default: "off"
112
- opt :hide_default_connectors, "make default connectors transparent: on or off", default: "off"
113
- opt :direction, "Tree layout direction: ttb (top-to-bottom) or ltr (left-to-right)", default: "ttb"
102
+ opt :outdir, "Output directory", default: "./", short: :o
103
+ opt :outfilename, "Output file base name", default: "syntree", short: :u
104
+ opt :format, "Output format: png, jpg, gif, pdf, svg, or lsif", default: "png", short: :f
105
+ opt :leafstyle, "visual style of tree leaves: auto, triangle, bar, or nothing", default: "auto", short: :l
106
+ opt :fontstyle, "Font style: sans, serif, cjk, mono", default: "sans", short: :n
107
+ opt :fontsize, "Size: 8-26", default: 16, short: :s
108
+ opt :linewidth, "Size: 1-5", default: 1, short: :i
109
+ opt :vheight, "Connector Height: 0.5-5.0", default: 2.0, short: :v
110
+ opt :color, "Color text and bars: modern, traditional, or off", default: "modern", short: :c
111
+ opt :symmetrize, "DEPRECATED alias of --tidy symmetric: on or off", default: "off", short: :y
112
+ opt :transparent, "Make background transparent: on or off", default: "off", short: :r
113
+ opt :polyline, "draw polyline connectors: on or off", default: "off", short: :p
114
+ opt :hide_default_connectors, "make default connectors transparent: on or off", default: "off", short: :none
115
+ opt :mirror, "flip the tree horizontally (RTL linguistics convention: first word at the right edge): on or off", default: "off"
116
+ 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
117
+ opt :hspacing, "Horizontal spacing factor (counterpart of vheight): 0.5-3.0", default: 1.0, short: :none
118
+ opt :tidy_spacing, "DEPRECATED alias of --hspacing: 0.5-3.0", default: 1.0, short: :none
119
+ opt :direction, "Tree layout direction: ttb (top-to-bottom) or ltr (left-to-right)", default: "ttb", short: :d
114
120
  opt :help, "This is a custom help message", short: :h
115
121
  end
116
122
 
@@ -119,7 +125,6 @@ Optimist.die :direction, "must be ttb or ltr" unless /\A(ttb|ltr)\z/ =~ opts[:di
119
125
  Optimist.die :format, "must be png, pdf, svg, or lsif" unless /\A(png|jpg|gif|pdf|svg|lsif)\z/ =~ opts[:format]
120
126
  Optimist.die :leafstyle, "must be auto, triangle, bar, or nothing" unless /\A(auto|triangle|bar|nothing)\z/ =~ opts[:leafstyle]
121
127
  Optimist.die :fontstyle, "must be sans, serif, cjk, or mono" unless /\A(sans|serif|cjk|mono)\z/ =~ opts[:fontstyle]
122
- Optimist.die :font, "must be path to an existing ttf font" if opts[:font] && !File.exist?(opts[:font])
123
128
  Optimist.die :fontsize, "must be in the range of 8-26" unless opts[:fontsize] >= 8 && opts[:fontsize] <= 26
124
129
  Optimist.die :linewidth, "must be in the range of 1-5" unless opts[:linewidth] >= 1 && opts[:linewidth] <= 5
125
130
  Optimist.die :color, "must be either modern, traditional, or off" unless /\A(modern|traditional|on|off|true|false)\z/ =~ opts[:color]
@@ -128,6 +133,10 @@ Optimist.die :vheight, "must be in the range of 0.5-5.0" if opts[:vheight] < 0.5
128
133
  Optimist.die :transparent, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:transparent]
129
134
  Optimist.die :polyline, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:polyline]
130
135
  Optimist.die :hide_default_connectors, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:hide_default_connectors]
136
+ Optimist.die :mirror, "must be either on or off" unless /\A(on|off|true|false)\z/ =~ opts[:mirror]
137
+ Optimist.die :tidy, "must be off, symmetric, low, medium, or high" unless /\A(off|symmetric|low|medium|high|on|compact|true|false)\z/ =~ opts[:tidy]
138
+ Optimist.die :hspacing, "must be in the range of 0.5-3.0" if opts[:hspacing] < 0.5 || opts[:hspacing] > 3.0
139
+ 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
131
140
 
132
141
  # Merge config file options with CLI options (CLI takes precedence)
133
142
  string_opts = {}
@@ -140,8 +149,8 @@ end
140
149
  # Then, apply CLI options (overrides config file)
141
150
  opts.each do |key, value|
142
151
  key_sym = key.to_sym
143
- # Skip internal optimist keys and nil font
144
- next if key.to_s.start_with?('_') || (key == :font && !value)
152
+ # Skip internal optimist keys
153
+ next if key.to_s.start_with?('_')
145
154
  # Only override if explicitly given on CLI or not in config
146
155
  if opts[:"#{key}_given"] || !file_config.key?(key_sym)
147
156
  string_opts[key_sym] = value