rsyntaxtree 1.6.3 → 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 (282) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +35 -0
  3. data/CHANGELOG.md +69 -0
  4. data/CITATION.cff +21 -0
  5. data/Dockerfile +4 -5
  6. data/Gemfile +1 -0
  7. data/README.md +24 -8
  8. data/bin/rsyntaxtree +29 -20
  9. data/lib/rsyntaxtree/base_graph.rb +381 -2
  10. data/lib/rsyntaxtree/element.rb +22 -17
  11. data/lib/rsyntaxtree/lsif_graph.rb +12 -2
  12. data/lib/rsyntaxtree/string_parser.rb +1 -1
  13. data/lib/rsyntaxtree/svg_graph.rb +13 -31
  14. data/lib/rsyntaxtree/utils.rb +70 -17
  15. data/lib/rsyntaxtree/version.rb +1 -1
  16. data/lib/rsyntaxtree.rb +60 -43
  17. data/rsyntaxtree.gemspec +7 -2
  18. metadata +18 -266
  19. data/dev/generate_examples.rb +0 -96
  20. data/dev/region_shade_proposal.md +0 -66
  21. data/docs/.gitignore +0 -5
  22. data/docs/404.html +0 -25
  23. data/docs/Gemfile +0 -33
  24. data/docs/_config.yml +0 -77
  25. data/docs/_examples/000.md +0 -28
  26. data/docs/_examples/001.md +0 -26
  27. data/docs/_examples/002.md +0 -35
  28. data/docs/_examples/003.md +0 -29
  29. data/docs/_examples/004.md +0 -29
  30. data/docs/_examples/005.md +0 -26
  31. data/docs/_examples/006.md +0 -29
  32. data/docs/_examples/007.md +0 -30
  33. data/docs/_examples/008.md +0 -32
  34. data/docs/_examples/009.md +0 -28
  35. data/docs/_examples/010.md +0 -52
  36. data/docs/_examples/011.md +0 -41
  37. data/docs/_examples/012.md +0 -44
  38. data/docs/_examples/013.md +0 -66
  39. data/docs/_examples/014.md +0 -51
  40. data/docs/_examples/015.md +0 -34
  41. data/docs/_examples/016.md +0 -86
  42. data/docs/_examples/017.md +0 -26
  43. data/docs/_examples/018.md +0 -32
  44. data/docs/_examples/019.md +0 -63
  45. data/docs/_examples/020.md +0 -43
  46. data/docs/_examples/021.md +0 -46
  47. data/docs/_examples/022.md +0 -61
  48. data/docs/_examples/023.md +0 -26
  49. data/docs/_examples/024.md +0 -26
  50. data/docs/_examples/025.md +0 -90
  51. data/docs/_examples/026.md +0 -30
  52. data/docs/_examples/027.md +0 -57
  53. data/docs/_examples/028.md +0 -23
  54. data/docs/_examples/029.md +0 -59
  55. data/docs/_examples/030.md +0 -36
  56. data/docs/_examples/031.md +0 -23
  57. data/docs/_examples/032.md +0 -30
  58. data/docs/_examples/033.md +0 -27
  59. data/docs/_examples/034.md +0 -29
  60. data/docs/_examples/035.md +0 -37
  61. data/docs/_examples/036.md +0 -20
  62. data/docs/_examples/037.md +0 -21
  63. data/docs/_examples/038.md +0 -33
  64. data/docs/_examples/039.md +0 -17
  65. data/docs/_examples/040.md +0 -63
  66. data/docs/_examples/041.md +0 -35
  67. data/docs/_examples/042.md +0 -22
  68. data/docs/_examples/043.md +0 -40
  69. data/docs/_examples/044.md +0 -16
  70. data/docs/_examples/045.md +0 -48
  71. data/docs/_examples/046.md +0 -440
  72. data/docs/_examples/047.md +0 -21
  73. data/docs/_examples/048.md +0 -71
  74. data/docs/_examples/049.md +0 -49
  75. data/docs/_examples/050.md +0 -96
  76. data/docs/_examples/051.md +0 -95
  77. data/docs/_examples/052.md +0 -58
  78. data/docs/_examples/053.md +0 -34
  79. data/docs/_examples/054.md +0 -22
  80. data/docs/_examples/055.md +0 -24
  81. data/docs/_examples/056.md +0 -25
  82. data/docs/_examples/057.md +0 -34
  83. data/docs/_examples/058.md +0 -47
  84. data/docs/_examples/059.md +0 -54
  85. data/docs/_examples/060.md +0 -72
  86. data/docs/_examples/061.md +0 -97
  87. data/docs/_examples/062.md +0 -96
  88. data/docs/_examples/063.md +0 -53
  89. data/docs/_examples/064.md +0 -25
  90. data/docs/_examples/065.md +0 -22
  91. data/docs/_includes/box_and_circle_table.html +0 -78
  92. data/docs/_includes/escape_char_table.html +0 -29
  93. data/docs/_includes/social_media_links.html +0 -13
  94. data/docs/_layouts/default.html +0 -43
  95. data/docs/assets/css/style.scss +0 -31
  96. data/docs/assets/img/000.png +0 -0
  97. data/docs/assets/img/001.png +0 -0
  98. data/docs/assets/img/002.png +0 -0
  99. data/docs/assets/img/003.png +0 -0
  100. data/docs/assets/img/004.png +0 -0
  101. data/docs/assets/img/005.png +0 -0
  102. data/docs/assets/img/006.png +0 -0
  103. data/docs/assets/img/007.png +0 -0
  104. data/docs/assets/img/008.png +0 -0
  105. data/docs/assets/img/009.png +0 -0
  106. data/docs/assets/img/010.png +0 -0
  107. data/docs/assets/img/011.png +0 -0
  108. data/docs/assets/img/012.png +0 -0
  109. data/docs/assets/img/013.png +0 -0
  110. data/docs/assets/img/014.png +0 -0
  111. data/docs/assets/img/015.png +0 -0
  112. data/docs/assets/img/016.png +0 -0
  113. data/docs/assets/img/017.png +0 -0
  114. data/docs/assets/img/018.png +0 -0
  115. data/docs/assets/img/019.png +0 -0
  116. data/docs/assets/img/020.png +0 -0
  117. data/docs/assets/img/021.png +0 -0
  118. data/docs/assets/img/022.png +0 -0
  119. data/docs/assets/img/023.png +0 -0
  120. data/docs/assets/img/024.png +0 -0
  121. data/docs/assets/img/025.png +0 -0
  122. data/docs/assets/img/026.png +0 -0
  123. data/docs/assets/img/027.png +0 -0
  124. data/docs/assets/img/028.png +0 -0
  125. data/docs/assets/img/029.png +0 -0
  126. data/docs/assets/img/030.png +0 -0
  127. data/docs/assets/img/031.png +0 -0
  128. data/docs/assets/img/032.png +0 -0
  129. data/docs/assets/img/033.png +0 -0
  130. data/docs/assets/img/034.png +0 -0
  131. data/docs/assets/img/035.png +0 -0
  132. data/docs/assets/img/036.png +0 -0
  133. data/docs/assets/img/037.png +0 -0
  134. data/docs/assets/img/038.png +0 -0
  135. data/docs/assets/img/039.png +0 -0
  136. data/docs/assets/img/040.png +0 -0
  137. data/docs/assets/img/041.png +0 -0
  138. data/docs/assets/img/042.png +0 -0
  139. data/docs/assets/img/043.png +0 -0
  140. data/docs/assets/img/044.png +0 -0
  141. data/docs/assets/img/045.png +0 -0
  142. data/docs/assets/img/046.png +0 -0
  143. data/docs/assets/img/047.png +0 -0
  144. data/docs/assets/img/048.png +0 -0
  145. data/docs/assets/img/049.png +0 -0
  146. data/docs/assets/img/050.png +0 -0
  147. data/docs/assets/img/051.png +0 -0
  148. data/docs/assets/img/052.png +0 -0
  149. data/docs/assets/img/053.png +0 -0
  150. data/docs/assets/img/054.png +0 -0
  151. data/docs/assets/img/055.png +0 -0
  152. data/docs/assets/img/056.png +0 -0
  153. data/docs/assets/img/057.png +0 -0
  154. data/docs/assets/img/058.png +0 -0
  155. data/docs/assets/img/059.png +0 -0
  156. data/docs/assets/img/060.png +0 -0
  157. data/docs/assets/img/061.png +0 -0
  158. data/docs/assets/img/062.png +0 -0
  159. data/docs/assets/img/063.png +0 -0
  160. data/docs/assets/img/064.png +0 -0
  161. data/docs/assets/img/065.png +0 -0
  162. data/docs/assets/img/elements/arrow_both.png +0 -0
  163. data/docs/assets/img/elements/arrow_both_bold.png +0 -0
  164. data/docs/assets/img/elements/arrow_left.png +0 -0
  165. data/docs/assets/img/elements/arrow_left_bold.png +0 -0
  166. data/docs/assets/img/elements/arrow_right.png +0 -0
  167. data/docs/assets/img/elements/arrow_right_bold.png +0 -0
  168. data/docs/assets/img/elements/circle.png +0 -0
  169. data/docs/assets/img/elements/circle_abc.png +0 -0
  170. data/docs/assets/img/elements/circle_bold.png +0 -0
  171. data/docs/assets/img/elements/circle_hatched.png +0 -0
  172. data/docs/assets/img/elements/circle_one.png +0 -0
  173. data/docs/assets/img/elements/connector.png +0 -0
  174. data/docs/assets/img/elements/connector_bold.png +0 -0
  175. data/docs/assets/img/elements/square.png +0 -0
  176. data/docs/assets/img/elements/square_abc.png +0 -0
  177. data/docs/assets/img/elements/square_bold.png +0 -0
  178. data/docs/assets/img/elements/square_hatched.png +0 -0
  179. data/docs/assets/img/elements/square_one.png +0 -0
  180. data/docs/assets/img/favicon/apple-touch-icon.png +0 -0
  181. data/docs/assets/img/favicon/favicon-16x16.png +0 -0
  182. data/docs/assets/img/favicon/favicon-32x32.png +0 -0
  183. data/docs/assets/img/favicon/favicon.ico +0 -0
  184. data/docs/assets/svg/000.svg +0 -74
  185. data/docs/assets/svg/001.svg +0 -48
  186. data/docs/assets/svg/002.svg +0 -74
  187. data/docs/assets/svg/003.svg +0 -63
  188. data/docs/assets/svg/004.svg +0 -77
  189. data/docs/assets/svg/005.svg +0 -50
  190. data/docs/assets/svg/006.svg +0 -56
  191. data/docs/assets/svg/007.svg +0 -64
  192. data/docs/assets/svg/008.svg +0 -72
  193. data/docs/assets/svg/009.svg +0 -70
  194. data/docs/assets/svg/010.svg +0 -94
  195. data/docs/assets/svg/011.svg +0 -58
  196. data/docs/assets/svg/012.svg +0 -64
  197. data/docs/assets/svg/013.svg +0 -212
  198. data/docs/assets/svg/014.svg +0 -190
  199. data/docs/assets/svg/015.svg +0 -69
  200. data/docs/assets/svg/016.svg +0 -810
  201. data/docs/assets/svg/017.svg +0 -86
  202. data/docs/assets/svg/018.svg +0 -75
  203. data/docs/assets/svg/019.svg +0 -235
  204. data/docs/assets/svg/020.svg +0 -110
  205. data/docs/assets/svg/021.svg +0 -95
  206. data/docs/assets/svg/022.svg +0 -172
  207. data/docs/assets/svg/023.svg +0 -125
  208. data/docs/assets/svg/024.svg +0 -56
  209. data/docs/assets/svg/025.svg +0 -179
  210. data/docs/assets/svg/026.svg +0 -50
  211. data/docs/assets/svg/027.svg +0 -101
  212. data/docs/assets/svg/028.svg +0 -51
  213. data/docs/assets/svg/029.svg +0 -122
  214. data/docs/assets/svg/030.svg +0 -76
  215. data/docs/assets/svg/031.svg +0 -42
  216. data/docs/assets/svg/032.svg +0 -85
  217. data/docs/assets/svg/033.svg +0 -81
  218. data/docs/assets/svg/034.svg +0 -88
  219. data/docs/assets/svg/035.svg +0 -77
  220. data/docs/assets/svg/036.svg +0 -47
  221. data/docs/assets/svg/037.svg +0 -68
  222. data/docs/assets/svg/038.svg +0 -82
  223. data/docs/assets/svg/039.svg +0 -30
  224. data/docs/assets/svg/040.svg +0 -120
  225. data/docs/assets/svg/041.svg +0 -93
  226. data/docs/assets/svg/042.svg +0 -42
  227. data/docs/assets/svg/043.svg +0 -304
  228. data/docs/assets/svg/044.svg +0 -32
  229. data/docs/assets/svg/045.svg +0 -107
  230. data/docs/assets/svg/046.svg +0 -990
  231. data/docs/assets/svg/047.svg +0 -62
  232. data/docs/assets/svg/048.svg +0 -134
  233. data/docs/assets/svg/049.svg +0 -97
  234. data/docs/assets/svg/050.svg +0 -193
  235. data/docs/assets/svg/051.svg +0 -179
  236. data/docs/assets/svg/052.svg +0 -96
  237. data/docs/assets/svg/053.svg +0 -81
  238. data/docs/assets/svg/054.svg +0 -51
  239. data/docs/assets/svg/055.svg +0 -54
  240. data/docs/assets/svg/056.svg +0 -71
  241. data/docs/assets/svg/057.svg +0 -91
  242. data/docs/assets/svg/058.svg +0 -95
  243. data/docs/assets/svg/059.svg +0 -111
  244. data/docs/assets/svg/060.svg +0 -134
  245. data/docs/assets/svg/061.svg +0 -193
  246. data/docs/assets/svg/062.svg +0 -179
  247. data/docs/assets/svg/063.svg +0 -94
  248. data/docs/assets/svg/064.svg +0 -63
  249. data/docs/assets/svg/065.svg +0 -54
  250. data/docs/documentation.md +0 -303
  251. data/docs/documentation_ja.md +0 -313
  252. data/docs/examples.html +0 -76
  253. data/docs/index.html +0 -2
  254. data/fonts/NotoSans-Bold.ttf +0 -0
  255. data/fonts/NotoSans-BoldItalic.ttf +0 -0
  256. data/fonts/NotoSans-Italic.ttf +0 -0
  257. data/fonts/NotoSans-Regular.ttf +0 -0
  258. data/fonts/NotoSansJP-Bold.otf +0 -0
  259. data/fonts/NotoSansJP-Regular.otf +0 -0
  260. data/fonts/NotoSansMath-Regular.ttf +0 -0
  261. data/fonts/NotoSansMono_SemiCondensed-Bold.ttf +0 -0
  262. data/fonts/NotoSansMono_SemiCondensed-Regular.ttf +0 -0
  263. data/fonts/NotoSerif-Bold.ttf +0 -0
  264. data/fonts/NotoSerif-BoldItalic.ttf +0 -0
  265. data/fonts/NotoSerif-Italic.ttf +0 -0
  266. data/fonts/NotoSerif-Regular.ttf +0 -0
  267. data/fonts/NotoSerifJP-Bold.otf +0 -0
  268. data/fonts/NotoSerifJP-Regular.otf +0 -0
  269. data/fonts/OpenMoji-black-glyf.ttf +0 -0
  270. data/fonts/wqy-zenhei.ttf +0 -0
  271. data/img/rsyntaxtree-web-screenshot.png +0 -0
  272. data/img/rsyntaxtree.png +0 -0
  273. data/img/sample.png +0 -0
  274. data/img/sample.svg +0 -38
  275. data/test/cli_test.rb +0 -262
  276. data/test/example_verify_test.rb +0 -79
  277. data/test/format_converter_test.rb +0 -129
  278. data/test/lsif_test.rb +0 -304
  279. data/test/markup_parser_test.rb +0 -260
  280. data/test/node_styling_test.rb +0 -377
  281. data/test/overlap_test.rb +0 -122
  282. data/test/tikz_test.rb +0 -147
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd3b753fd33f6df5903c6df87d02bf7c7ac45de57e1ba18da51f03fe391ab45f
4
- data.tar.gz: 3f53988bbb1ccd64b75792b822ccb04eca2d7f209500d881610cf2295ced3ce7
3
+ metadata.gz: 27e1c2eb4e8e276a63ab92e99b32bd3ef607451df95088f379ff093f6a206001
4
+ data.tar.gz: 5147922ca1269a629761d7b93f364f9ae9d22fc41d63ce28fdd4c75223cccc52
5
5
  SHA512:
6
- metadata.gz: 66e9a66413e457d4de55902743cc9ad61861c4b7fcdb4d9951d79966893e0665f5fea1e369ec69bbf058104627fc156250c841e2213f22b55b1c5b306991f311
7
- data.tar.gz: 94ff87d7fab49605087c81bc98639c86fe768c1842048438c87381d22ebbf4d651cc0b5b6cadc16acf169cdee9021b694a8239777bc6088a4d3eb3d5f985d248
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,74 @@
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
+
52
+ ## [1.7.0] - 2026-08
53
+
54
+ ### Changed
55
+ - Text measurement now uses Pango, the same engine (and the same fontconfig
56
+ font-fallback resolution) librsvg uses to render the output, instead of
57
+ RMagick with the bundled font files. Labels in any script are measured with
58
+ the font that actually draws them, which fixes off-center labels for scripts
59
+ the bundled fonts do not cover (e.g. Khmer, #14) without bundling per-script
60
+ fonts. Horizontal dimensions of the output may change slightly.
61
+ - Vertical rhythm is now derived deterministically from the font size
62
+ (1.4 x size, matching the previous Latin line height) and is identical
63
+ across scripts; the old engine spaced Japanese text 1.5x and WenQuanYi
64
+ 1.25x, so mixed-language documents had inconsistent spacing.
65
+ - The font-family lists used in the SVG output and in measurement are now
66
+ defined in a single place (`FONT_FAMILIES` / `FontFamily`).
67
+
68
+ ### Added
69
+ - Runtime dependency on the `pango` gem (ruby-gnome). No new system
70
+ requirements: librsvg already depends on Pango.
71
+
3
72
  ## [1.6.3] - 2026-07
4
73
 
5
74
  ### Fixed
data/CITATION.cff ADDED
@@ -0,0 +1,21 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use RSyntaxTree in your research, please cite it as below."
3
+ type: software
4
+ title: "RSyntaxTree: A graphical syntax tree image generator"
5
+ authors:
6
+ - family-names: "Hasebe"
7
+ given-names: "Yoichiro"
8
+ email: "yohasebe@gmail.com"
9
+ url: "https://yohasebe.com/rsyntaxtree"
10
+ repository-code: "https://github.com/yohasebe/rsyntaxtree"
11
+ # Concept DOI: always resolves to the latest archived version on Zenodo
12
+ doi: "10.5281/zenodo.21916150"
13
+ license: MIT
14
+ version: 1.8.0
15
+ date-released: 2026-08-15
16
+ keywords:
17
+ - linguistics
18
+ - syntax tree
19
+ - syntactic analysis
20
+ - visualization
21
+ - Ruby
data/Dockerfile CHANGED
@@ -5,18 +5,17 @@ WORKDIR $WORKSPACE
5
5
  RUN apk update && \
6
6
  apk upgrade && \
7
7
  apk add --no-cache linux-headers libxml2-dev make gcc libc-dev bash && \
8
- apk add --no-cache librsvg librsvg-dev imagemagick imagemagick-dev xz-dev libbz2 && \
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/Gemfile CHANGED
@@ -3,6 +3,7 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "optimist"
6
+ gem "pango"
6
7
  gem "parslet"
7
8
  gem "rmagick"
8
9
  gem "rsvg2"
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
 
@@ -189,16 +201,20 @@ For detailed documentation on syntax and markup:
189
201
  - [日本語ドキュメント](https://yohasebe.github.io/rsyntaxtree/documentation_ja)
190
202
  - [Example Gallery](https://yohasebe.github.io/rsyntaxtree/examples)
191
203
 
192
- ## References
204
+ ## How to Cite
205
+
206
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.21916150.svg?v=1)](https://doi.org/10.5281/zenodo.21916150)
193
207
 
194
- Please use the following BibTeX entry when citing RSyntaxTree:
208
+ If you use RSyntaxTree in your research, please cite it. You can use the "Cite this repository" button on GitHub (powered by [CITATION.cff](CITATION.cff)), or the following BibTeX entry (adjust `version` to the one you used):
195
209
 
196
210
  ```bibtex
197
- @misc{rsyntaxtree,
198
- author = {Yoichiro Hasebe},
199
- title = {RSyntaxTree: A graphical syntax tree image generator},
200
- url = {https://yohasebe.com/rsyntaxtree},
201
- year = {2026}
211
+ @software{hasebe_rsyntaxtree,
212
+ author = {Hasebe, Yoichiro},
213
+ title = {RSyntaxTree: A graphical syntax tree image generator},
214
+ url = {https://yohasebe.com/rsyntaxtree},
215
+ doi = {10.5281/zenodo.21916150},
216
+ version = {1.8.0},
217
+ year = {2026}
202
218
  }
203
219
  ```
204
220
 
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