aa2img 0.1.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 +7 -0
- data/CHANGELOG.md +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +247 -0
- data/Rakefile +8 -0
- data/examples/basic_usage.rb +21 -0
- data/examples/sample_diagrams/architecture.txt +10 -0
- data/examples/sample_diagrams/flowchart.txt +3 -0
- data/exe/aa2img +6 -0
- data/lib/aa2img/ast/annotation.rb +9 -0
- data/lib/aa2img/ast/base_element.rb +13 -0
- data/lib/aa2img/ast/box.rb +43 -0
- data/lib/aa2img/ast/edge.rb +15 -0
- data/lib/aa2img/ast/label.rb +14 -0
- data/lib/aa2img/ast/scene.rb +79 -0
- data/lib/aa2img/ast/section.rb +16 -0
- data/lib/aa2img/char_width.rb +15 -0
- data/lib/aa2img/cli.rb +44 -0
- data/lib/aa2img/grid.rb +35 -0
- data/lib/aa2img/layout/calculator.rb +97 -0
- data/lib/aa2img/layout/font_metrics.rb +22 -0
- data/lib/aa2img/layout/grid_metrics.rb +22 -0
- data/lib/aa2img/parser/arrow_detector.rb +136 -0
- data/lib/aa2img/parser/box_builder.rb +51 -0
- data/lib/aa2img/parser/char_classifier.rb +68 -0
- data/lib/aa2img/parser/corner_detector.rb +70 -0
- data/lib/aa2img/parser/edge_tracer.rb +114 -0
- data/lib/aa2img/parser/nesting_analyzer.rb +20 -0
- data/lib/aa2img/parser/section_detector.rb +45 -0
- data/lib/aa2img/parser/text_extractor.rb +78 -0
- data/lib/aa2img/parser.rb +48 -0
- data/lib/aa2img/renderer/base.rb +11 -0
- data/lib/aa2img/renderer/png_renderer.rb +25 -0
- data/lib/aa2img/renderer/svg_renderer.rb +148 -0
- data/lib/aa2img/theme.rb +130 -0
- data/lib/aa2img/version.rb +5 -0
- data/lib/aa2img.rb +65 -0
- data/themes/blueprint.yml +18 -0
- data/themes/default.yml +18 -0
- data/themes/modern.yml +18 -0
- data/themes/monochrome.yml +18 -0
- metadata +140 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: cffb5142c6353b13b54d484e6879b928bf2618ca9b48263506dfec84d9addacd
|
|
4
|
+
data.tar.gz: 9309a77b67f39e24c44b42243ee9b057132527fae99e3a86a8131d2de6df1383
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4260c2b672c7f533008bb53bc016e1420392e80adef03bcdc63cff64688e326dee7ea3bce363991e0b8a402ce9ea3ccf3e8005ff9bf5e3f959c377faaa340cc1
|
|
7
|
+
data.tar.gz: 0f6d68dd8ce1f000f56925606ca9c83fe67debaff6dcfb8ef05750942060c8f19ab9c03e526370bdd61dcdd8f70f73884a0a47db65bcd205ac0a2a9bb02d5326
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## Unreleased
|
|
9
|
+
|
|
10
|
+
## 0.1.0 (2026-02-07)
|
|
11
|
+
|
|
12
|
+
- Initial release.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yudai Takada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
<h1 align="center">aa2img</h1>
|
|
2
|
+
<p align="center">
|
|
3
|
+
<strong>Convert ASCII/Unicode diagrams into clean SVG and PNG images</strong>
|
|
4
|
+
</p>
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="https://img.shields.io/badge/ruby-%3E%3D%203.2-red.svg" alt="Ruby 3.2+">
|
|
7
|
+
<img src="https://img.shields.io/badge/output-SVG%20%2F%20PNG-2f855a.svg" alt="Output SVG/PNG">
|
|
8
|
+
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License MIT">
|
|
9
|
+
<a href="https://github.com/ydah/aa2img/actions/workflows/main.yml">
|
|
10
|
+
<img src="https://github.com/ydah/aa2img/actions/workflows/main.yml/badge.svg" alt="CI">
|
|
11
|
+
</a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<a href="#features">Features</a> •
|
|
16
|
+
<a href="#installation">Installation</a> •
|
|
17
|
+
<a href="#usage">Usage</a> •
|
|
18
|
+
<a href="#diagram-syntax">Diagram Syntax</a> •
|
|
19
|
+
<a href="#themes">Themes</a>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
`aa2img` parses text-based box diagrams and renders them as themed images.
|
|
25
|
+
It is useful for docs, README files, and architecture notes where you want to keep diagrams editable as plain text.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- Parses both Unicode and ASCII box-drawing styles (`┌─┐ │ └─┘` and `+--+ | | +--+`)
|
|
30
|
+
- Detects section separators (`├──┤`) inside boxes
|
|
31
|
+
- Detects nested parent-child box structures
|
|
32
|
+
- Renders right-side annotations in `← note` format
|
|
33
|
+
- Handles full-width characters, including Japanese labels
|
|
34
|
+
- Supports `SVG` and `PNG` output
|
|
35
|
+
- Ships with 4 built-in themes: `default`, `blueprint`, `monochrome`, `modern`
|
|
36
|
+
- Supports vertical label alignment: `top`, `center`, `bottom`
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
### Requirements
|
|
41
|
+
|
|
42
|
+
- Ruby `3.2+`
|
|
43
|
+
- ImageMagick (required only for PNG output)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# macOS
|
|
47
|
+
brew install imagemagick
|
|
48
|
+
|
|
49
|
+
# Ubuntu / Debian
|
|
50
|
+
sudo apt install imagemagick
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Add to Gemfile
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
gem "aa2img", github: "ydah/aa2img"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
After the gem is published to RubyGems, you can also install it with:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
gem install aa2img
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
### Quick Start (CLI)
|
|
68
|
+
|
|
69
|
+
1. Create a text diagram file:
|
|
70
|
+
|
|
71
|
+
```txt
|
|
72
|
+
┌────────────────────────────┐
|
|
73
|
+
│ API Gateway │
|
|
74
|
+
├────────────────────────────┤
|
|
75
|
+
│ Application │
|
|
76
|
+
│ ┌──────────────────────┐ │
|
|
77
|
+
│ │ Database │ │
|
|
78
|
+
│ └──────────────────────┘ │
|
|
79
|
+
└────────────────────────────┘
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
2. Convert to SVG:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
aa2img convert diagram.txt output.svg
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
3. Convert to PNG:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
aa2img convert diagram.txt output.png --scale 3
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Commands
|
|
95
|
+
|
|
96
|
+
| Command | Description |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `aa2img convert INPUT_FILE OUTPUT_FILE` | Convert text diagram to image (format inferred from extension) |
|
|
99
|
+
| `aa2img themes` | List available themes |
|
|
100
|
+
| `aa2img preview INPUT_FILE` | Show parsed AST tree for debugging |
|
|
101
|
+
| `aa2img version` | Show version |
|
|
102
|
+
|
|
103
|
+
### `convert` Options
|
|
104
|
+
|
|
105
|
+
| Option | Description |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `--theme NAME_OR_PATH` | Built-in theme name (`default`, `blueprint`, `monochrome`, `modern`) or YAML file path |
|
|
108
|
+
| `--scale N` | Scale factor for PNG output (default: `2`) |
|
|
109
|
+
| `--valign POS` | Vertical label alignment (`top`, `center`, `bottom`) |
|
|
110
|
+
|
|
111
|
+
Notes:
|
|
112
|
+
- Use `-` as `INPUT_FILE` to read from stdin.
|
|
113
|
+
- Output format is inferred from `OUTPUT_FILE` extension (`.svg` or `.png`).
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Read from stdin
|
|
117
|
+
cat diagram.txt | aa2img convert - output.svg
|
|
118
|
+
|
|
119
|
+
# Use blueprint theme
|
|
120
|
+
aa2img convert diagram.txt blueprint.svg --theme blueprint
|
|
121
|
+
|
|
122
|
+
# Use custom theme file
|
|
123
|
+
aa2img convert diagram.txt custom.svg --theme ./themes/my-theme.yml
|
|
124
|
+
|
|
125
|
+
# Center label alignment
|
|
126
|
+
aa2img convert diagram.txt centered.svg --valign center
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Ruby API
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
require "aa2img"
|
|
133
|
+
|
|
134
|
+
aa = <<~AA
|
|
135
|
+
┌──────┐
|
|
136
|
+
│ Test │
|
|
137
|
+
└──────┘
|
|
138
|
+
AA
|
|
139
|
+
|
|
140
|
+
# Get SVG string
|
|
141
|
+
svg = AA2img.convert(aa, format: :svg, theme: "default", valign: :center)
|
|
142
|
+
|
|
143
|
+
# Write to file (format inferred from extension)
|
|
144
|
+
AA2img.convert_to_file(aa, "diagram.png", theme: "monochrome", scale: 2, valign: :top)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Diagram Syntax
|
|
148
|
+
|
|
149
|
+
### Basic Boxes
|
|
150
|
+
|
|
151
|
+
Unicode:
|
|
152
|
+
|
|
153
|
+
```txt
|
|
154
|
+
┌──────┐
|
|
155
|
+
│ API │
|
|
156
|
+
└──────┘
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
ASCII:
|
|
160
|
+
|
|
161
|
+
```txt
|
|
162
|
+
+------+
|
|
163
|
+
| API |
|
|
164
|
+
+------+
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Sectioned Boxes
|
|
168
|
+
|
|
169
|
+
```txt
|
|
170
|
+
┌────────────────┐
|
|
171
|
+
│ Presentation │
|
|
172
|
+
├────────────────┤
|
|
173
|
+
│ Application │
|
|
174
|
+
├────────────────┤
|
|
175
|
+
│ Infrastructure │
|
|
176
|
+
└────────────────┘
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Annotations
|
|
180
|
+
|
|
181
|
+
```txt
|
|
182
|
+
┌──────────┐ ← user input
|
|
183
|
+
│ REPL │
|
|
184
|
+
└──────────┘
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Themes
|
|
188
|
+
|
|
189
|
+
Built-in themes:
|
|
190
|
+
|
|
191
|
+
- `default`: balanced light theme
|
|
192
|
+
- `blueprint`: deep-blue blueprint-like style
|
|
193
|
+
- `monochrome`: print-friendly grayscale style
|
|
194
|
+
- `modern`: clean, rounded, modern UI style
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
aa2img themes
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
CLI also accepts a YAML path for custom themes:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
aa2img convert diagram.txt output.svg --theme ./themes/my-theme.yml
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
You can also pass a custom theme hash from Ruby:
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
custom_theme = {
|
|
210
|
+
"background_color" => "#ffffff",
|
|
211
|
+
"box_fill" => "#f8fafc",
|
|
212
|
+
"box_stroke" => "#334155",
|
|
213
|
+
"text_color" => "#0f172a",
|
|
214
|
+
"font_family" => "'Noto Sans JP', sans-serif"
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
svg = AA2img.convert(aa, format: :svg, theme: custom_theme)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Notes
|
|
221
|
+
|
|
222
|
+
- Current parser behavior is optimized for box/layer diagrams.
|
|
223
|
+
- Annotation detection currently supports right-side `←` markers.
|
|
224
|
+
- `preview --format` is reserved for future expansion; current output is tree-style.
|
|
225
|
+
|
|
226
|
+
## Development
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
git clone https://github.com/ydah/aa2img.git
|
|
230
|
+
cd aa2img
|
|
231
|
+
bin/setup
|
|
232
|
+
bundle exec rspec
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Run the example script:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
bundle exec ruby examples/basic_usage.rb
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Contributing
|
|
242
|
+
|
|
243
|
+
Issues and pull requests are welcome at `https://github.com/ydah/aa2img`.
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
[MIT License](LICENSE.txt)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "aa2img"
|
|
5
|
+
|
|
6
|
+
aa_text = <<~'AA'
|
|
7
|
+
┌─────────────────────────────────────┐
|
|
8
|
+
│ REPL (UI) │
|
|
9
|
+
├─────────────────────────────────────┤
|
|
10
|
+
│ Command Parser │
|
|
11
|
+
├─────────────────────────────────────┤
|
|
12
|
+
│ Database │
|
|
13
|
+
│ ┌─────────────────────────────┐ │
|
|
14
|
+
│ │ StringHashMap │ │
|
|
15
|
+
│ └─────────────────────────────┘ │
|
|
16
|
+
└─────────────────────────────────────┘
|
|
17
|
+
AA
|
|
18
|
+
|
|
19
|
+
svg = AA2img.convert(aa_text, format: :svg, valign: :center, theme: :modern)
|
|
20
|
+
File.write("output.svg", svg)
|
|
21
|
+
puts "Generated output.svg"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
┌─────────────────────────────────────┐
|
|
2
|
+
│ REPL (UI) │ ← ユーザーとの対話
|
|
3
|
+
├─────────────────────────────────────┤
|
|
4
|
+
│ Command Parser │ ← コマンド解析
|
|
5
|
+
├─────────────────────────────────────┤
|
|
6
|
+
│ Database │ ← コア機能
|
|
7
|
+
│ ┌─────────────────────────────┐ │
|
|
8
|
+
│ │ StringHashMap │ │ ← データ保存
|
|
9
|
+
│ └─────────────────────────────┘ │
|
|
10
|
+
└─────────────────────────────────────┘
|
data/exe/aa2img
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AA2img
|
|
4
|
+
module AST
|
|
5
|
+
class Box < BaseElement
|
|
6
|
+
attr_accessor :top, :left, :bottom, :right
|
|
7
|
+
attr_accessor :sections, :children, :labels, :annotations
|
|
8
|
+
|
|
9
|
+
def initialize(**attrs)
|
|
10
|
+
@sections = []
|
|
11
|
+
@children = []
|
|
12
|
+
@labels = []
|
|
13
|
+
@annotations = []
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def area
|
|
18
|
+
(bottom - top) * (right - left)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def contains?(other)
|
|
22
|
+
top < other.top && bottom > other.bottom &&
|
|
23
|
+
left < other.left && right > other.right
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def add_child(child)
|
|
27
|
+
@children << child
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def all_labels
|
|
31
|
+
own = labels || []
|
|
32
|
+
from_sections = (sections || []).flat_map { |s| s.labels || [] }
|
|
33
|
+
own + from_sections
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def all_annotations
|
|
37
|
+
own = annotations || []
|
|
38
|
+
from_sections = (sections || []).flat_map { |s| s.annotation ? [s.annotation] : [] }
|
|
39
|
+
own + from_sections
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AA2img
|
|
4
|
+
module AST
|
|
5
|
+
class Scene
|
|
6
|
+
attr_accessor :elements, :width, :height
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@elements = []
|
|
10
|
+
@width = 0
|
|
11
|
+
@height = 0
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def boxes
|
|
15
|
+
elements.select { |e| e.is_a?(Box) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def edges
|
|
19
|
+
elements.select { |e| e.is_a?(Edge) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def all_boxes
|
|
23
|
+
result = []
|
|
24
|
+
boxes.each do |box|
|
|
25
|
+
result << box
|
|
26
|
+
result.concat(collect_nested_boxes(box))
|
|
27
|
+
end
|
|
28
|
+
result
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_tree_string(indent = 0)
|
|
32
|
+
lines = []
|
|
33
|
+
prefix = " " * indent
|
|
34
|
+
lines << "#{prefix}Scene (width: #{width}, height: #{height})"
|
|
35
|
+
elements.each do |el|
|
|
36
|
+
lines << element_to_tree(el, indent + 1)
|
|
37
|
+
end
|
|
38
|
+
lines.join("\n")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def collect_nested_boxes(box)
|
|
44
|
+
result = []
|
|
45
|
+
(box.children || []).each do |child|
|
|
46
|
+
result << child
|
|
47
|
+
result.concat(collect_nested_boxes(child))
|
|
48
|
+
end
|
|
49
|
+
result
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def element_to_tree(el, indent)
|
|
53
|
+
prefix = " " * indent
|
|
54
|
+
case el
|
|
55
|
+
when Box
|
|
56
|
+
box_to_tree(el, indent)
|
|
57
|
+
when Edge
|
|
58
|
+
"#{prefix}Edge #{el.from} -> #{el.to} (#{el.style}, #{el.arrow})"
|
|
59
|
+
else
|
|
60
|
+
"#{prefix}#{el.class.name}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def box_to_tree(box, indent)
|
|
65
|
+
prefix = " " * indent
|
|
66
|
+
lines = ["#{prefix}└── Box (#{box.top},#{box.left})-(#{box.bottom},#{box.right})"]
|
|
67
|
+
(box.sections || []).each_with_index do |section, i|
|
|
68
|
+
section_labels = (section.labels || []).map(&:text).join(", ")
|
|
69
|
+
ann_text = section.annotation ? " + annotation \"#{section.annotation.text}\"" : ""
|
|
70
|
+
lines << "#{prefix} ├── Section[#{i}] (row #{section.top}..#{section.bottom}): \"#{section_labels}\"#{ann_text}"
|
|
71
|
+
end
|
|
72
|
+
(box.children || []).each do |child|
|
|
73
|
+
lines << box_to_tree(child, indent + 2)
|
|
74
|
+
end
|
|
75
|
+
lines.join("\n")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AA2img
|
|
4
|
+
module AST
|
|
5
|
+
class Section < BaseElement
|
|
6
|
+
attr_accessor :top, :bottom
|
|
7
|
+
attr_accessor :labels, :annotation
|
|
8
|
+
|
|
9
|
+
def initialize(**attrs)
|
|
10
|
+
@labels = []
|
|
11
|
+
@annotation = nil
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/aa2img/cli.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
module AA2img
|
|
6
|
+
class CLI < Thor
|
|
7
|
+
desc "convert INPUT_FILE OUTPUT_FILE", "Convert AA text file to image"
|
|
8
|
+
option :theme, type: :string, default: "default", desc: "Theme name or YAML path"
|
|
9
|
+
option :scale, type: :numeric, default: 2, desc: "PNG scale factor"
|
|
10
|
+
option :valign, type: :string, default: "top", desc: "Vertical text alignment (top / center / bottom)"
|
|
11
|
+
def convert(input_file, output_file)
|
|
12
|
+
text = if input_file == "-"
|
|
13
|
+
$stdin.read
|
|
14
|
+
else
|
|
15
|
+
File.read(input_file)
|
|
16
|
+
end
|
|
17
|
+
AA2img.convert_to_file(text, output_file,
|
|
18
|
+
theme: options[:theme],
|
|
19
|
+
scale: options[:scale],
|
|
20
|
+
valign: options[:valign].to_sym)
|
|
21
|
+
puts "✓ #{output_file} generated"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc "themes", "List available themes"
|
|
25
|
+
def themes
|
|
26
|
+
Theme.available.each { |t| puts " - #{t}" }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
desc "preview INPUT_FILE", "Parse AA and display AST (debug)"
|
|
30
|
+
option :format, type: :string, default: "tree", desc: "Output format (tree / json)"
|
|
31
|
+
def preview(input_file)
|
|
32
|
+
text = File.read(input_file)
|
|
33
|
+
grid = Grid.new(text)
|
|
34
|
+
parser = AA2img::Parser::Orchestrator.new(grid)
|
|
35
|
+
scene = parser.parse
|
|
36
|
+
puts scene.to_tree_string
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
desc "version", "Show version"
|
|
40
|
+
def version
|
|
41
|
+
puts "aa2img #{AA2img::VERSION}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/aa2img/grid.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "unicode/display_width"
|
|
4
|
+
|
|
5
|
+
module AA2img
|
|
6
|
+
class Grid
|
|
7
|
+
attr_reader :rows, :width, :height
|
|
8
|
+
|
|
9
|
+
def initialize(text)
|
|
10
|
+
lines = text.lines.map(&:chomp)
|
|
11
|
+
@height = lines.size
|
|
12
|
+
@rows = lines.map { |line| normalize_line(line) }
|
|
13
|
+
@width = @rows.map(&:size).max || 0
|
|
14
|
+
@rows.each { |row| row.fill(" ", row.size...@width) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def at(row, col)
|
|
18
|
+
return nil if row < 0 || row >= @height || col < 0 || col >= @width
|
|
19
|
+
|
|
20
|
+
@rows[row][col]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def normalize_line(line)
|
|
26
|
+
cells = []
|
|
27
|
+
line.each_char do |ch|
|
|
28
|
+
w = Unicode::DisplayWidth.of(ch)
|
|
29
|
+
cells << ch
|
|
30
|
+
(w - 1).times { cells << :wide_placeholder }
|
|
31
|
+
end
|
|
32
|
+
cells
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|