tessella 0.3.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/LICENSE +21 -0
- data/README.md +900 -0
- data/doc/README.md +74 -0
- data/doc/bitfield.md +123 -0
- data/doc/charts.md +1370 -0
- data/doc/check.rb +75 -0
- data/doc/cli.md +483 -0
- data/doc/composition.md +599 -0
- data/doc/getting-started.md +121 -0
- data/doc/glyphs.md +134 -0
- data/doc/images.md +325 -0
- data/doc/library.md +239 -0
- data/doc/lint.md +270 -0
- data/doc/meter.md +210 -0
- data/doc/rule.md +51 -0
- data/doc/seq.md +169 -0
- data/doc/sparkline.md +63 -0
- data/doc/table.md +347 -0
- data/doc/targets.md +222 -0
- data/doc/text.md +84 -0
- data/doc/tree.md +108 -0
- data/doc/wave.md +122 -0
- data/examples/README.md +103 -0
- data/examples/ages.txt +9 -0
- data/examples/bad.csv +3 -0
- data/examples/boxes.txt +3 -0
- data/examples/broken.txt +4 -0
- data/examples/calibration.rb +12 -0
- data/examples/cities.csv +4 -0
- data/examples/cjk.rb +13 -0
- data/examples/comment.rb +9 -0
- data/examples/comment.sh +13 -0
- data/examples/cpu.txt +60 -0
- data/examples/dashboard.sh +25 -0
- data/examples/datasheet.rb +16 -0
- data/examples/disc.pgm +0 -0
- data/examples/disks.sh +18 -0
- data/examples/disks.txt +5 -0
- data/examples/dose.txt +7 -0
- data/examples/drift.txt +4 -0
- data/examples/ethernet.txt +3 -0
- data/examples/figure.c +5 -0
- data/examples/fit.rb +16 -0
- data/examples/floors.rb +21 -0
- data/examples/gallery.sh +20 -0
- data/examples/gauges.sh +31 -0
- data/examples/gradient.rb +16 -0
- data/examples/growth.txt +6 -0
- data/examples/hosts.csv +4 -0
- data/examples/icmp.txt +3 -0
- data/examples/ipv4.txt +12 -0
- data/examples/kinds.csv +4 -0
- data/examples/langs.csv +4 -0
- data/examples/lint.rb +16 -0
- data/examples/load.rb +14 -0
- data/examples/login.txt +10 -0
- data/examples/neg.csv +4 -0
- data/examples/normal.txt +200 -0
- data/examples/note.txt +2 -0
- data/examples/notes-fixed.md +17 -0
- data/examples/notes.md +17 -0
- data/examples/ohlc.txt +7 -0
- data/examples/os.csv +10 -0
- data/examples/outline.txt +8 -0
- data/examples/plot.txt +5 -0
- data/examples/refusals.rb +17 -0
- data/examples/report.sh +21 -0
- data/examples/rfc.sh +22 -0
- data/examples/samples.txt +7 -0
- data/examples/shapes.pgm +0 -0
- data/examples/sizes.tsv +3 -0
- data/examples/spi.txt +6 -0
- data/examples/status.sh +23 -0
- data/examples/stem.txt +12 -0
- data/examples/table-drift.txt +4 -0
- data/examples/temps.txt +31 -0
- data/examples/trig.sh +13 -0
- data/examples/trig.txt +102 -0
- data/examples/week.txt +8 -0
- data/examples/wide.txt +2 -0
- data/exe/tessella +6 -0
- data/lib/tessella/axis.rb +71 -0
- data/lib/tessella/bitfield.rb +194 -0
- data/lib/tessella/block.rb +56 -0
- data/lib/tessella/borders.rb +85 -0
- data/lib/tessella/canvas/area.rb +155 -0
- data/lib/tessella/canvas/ascii.rb +120 -0
- data/lib/tessella/canvas/block.rb +40 -0
- data/lib/tessella/canvas/braille.rb +30 -0
- data/lib/tessella/canvas/clip.rb +49 -0
- data/lib/tessella/canvas/density.rb +51 -0
- data/lib/tessella/canvas/dot.rb +27 -0
- data/lib/tessella/canvas/eighths.rb +43 -0
- data/lib/tessella/canvas/half.rb +51 -0
- data/lib/tessella/canvas/sextant.rb +36 -0
- data/lib/tessella/canvas.rb +168 -0
- data/lib/tessella/cell.rb +10 -0
- data/lib/tessella/chart.rb +211 -0
- data/lib/tessella/check.rb +52 -0
- data/lib/tessella/cli/bitfield.rb +121 -0
- data/lib/tessella/cli/chart.rb +615 -0
- data/lib/tessella/cli/chart_candle.rb +36 -0
- data/lib/tessella/cli/chart_heatmap.rb +21 -0
- data/lib/tessella/cli/chart_help.rb +85 -0
- data/lib/tessella/cli/chart_pie.rb +38 -0
- data/lib/tessella/cli/compose.rb +156 -0
- data/lib/tessella/cli/compose_options.rb +259 -0
- data/lib/tessella/cli/glyphs.rb +97 -0
- data/lib/tessella/cli/image.rb +250 -0
- data/lib/tessella/cli/input.rb +44 -0
- data/lib/tessella/cli/iso_time.rb +28 -0
- data/lib/tessella/cli/lint.rb +102 -0
- data/lib/tessella/cli/lint_options.rb +99 -0
- data/lib/tessella/cli/main.rb +121 -0
- data/lib/tessella/cli/meter.rb +80 -0
- data/lib/tessella/cli/rule.rb +57 -0
- data/lib/tessella/cli/seq.rb +117 -0
- data/lib/tessella/cli/sparkline.rb +72 -0
- data/lib/tessella/cli/table.rb +167 -0
- data/lib/tessella/cli/text.rb +78 -0
- data/lib/tessella/cli/tree.rb +100 -0
- data/lib/tessella/cli/wave.rb +148 -0
- data/lib/tessella/cli.rb +36 -0
- data/lib/tessella/color.rb +106 -0
- data/lib/tessella/compose.rb +243 -0
- data/lib/tessella/compose_fill.rb +176 -0
- data/lib/tessella/compose_grid.rb +36 -0
- data/lib/tessella/compose_shadow.rb +41 -0
- data/lib/tessella/error.rb +13 -0
- data/lib/tessella/frame.rb +170 -0
- data/lib/tessella/glyphs.rb +106 -0
- data/lib/tessella/image.rb +454 -0
- data/lib/tessella/image_octant.rb +46 -0
- data/lib/tessella/image_petscii.rb +116 -0
- data/lib/tessella/kinds/bar.rb +344 -0
- data/lib/tessella/kinds/box.rb +213 -0
- data/lib/tessella/kinds/box_upright.rb +110 -0
- data/lib/tessella/kinds/candlestick.rb +164 -0
- data/lib/tessella/kinds/columns.rb +259 -0
- data/lib/tessella/kinds/date_axis.rb +102 -0
- data/lib/tessella/kinds/error_bars.rb +71 -0
- data/lib/tessella/kinds/heatmap.rb +130 -0
- data/lib/tessella/kinds/line.rb +173 -0
- data/lib/tessella/kinds/multibar.rb +98 -0
- data/lib/tessella/kinds/multibar_columns.rb +130 -0
- data/lib/tessella/kinds/multibar_fine.rb +45 -0
- data/lib/tessella/kinds/multibar_rows.rb +93 -0
- data/lib/tessella/kinds/pie.rb +123 -0
- data/lib/tessella/kinds/pie_rim.rb +78 -0
- data/lib/tessella/kinds/sideways.rb +62 -0
- data/lib/tessella/kinds/stem.rb +134 -0
- data/lib/tessella/kinds/threshold.rb +73 -0
- data/lib/tessella/legend.rb +31 -0
- data/lib/tessella/lint/alphabet.rb +56 -0
- data/lib/tessella/lint/drift.rb +82 -0
- data/lib/tessella/lint/fences.rb +51 -0
- data/lib/tessella/lint/geometry.rb +120 -0
- data/lib/tessella/lint/geometry_checks.rb +224 -0
- data/lib/tessella/lint/lines.rb +115 -0
- data/lib/tessella/lint/mixing.rb +61 -0
- data/lib/tessella/lint/runner.rb +73 -0
- data/lib/tessella/lint/shadows.rb +120 -0
- data/lib/tessella/lint/target_rows.rb +66 -0
- data/lib/tessella/lint.rb +158 -0
- data/lib/tessella/meter.rb +133 -0
- data/lib/tessella/meter_upright.rb +43 -0
- data/lib/tessella/png.rb +456 -0
- data/lib/tessella/raster.rb +92 -0
- data/lib/tessella/rmagick_reader.rb +52 -0
- data/lib/tessella/rule.rb +59 -0
- data/lib/tessella/seq.rb +522 -0
- data/lib/tessella/sparkline.rb +57 -0
- data/lib/tessella/stats.rb +199 -0
- data/lib/tessella/table.rb +547 -0
- data/lib/tessella/table_cells.rb +225 -0
- data/lib/tessella/target.rb +105 -0
- data/lib/tessella/text.rb +76 -0
- data/lib/tessella/text_forms.rb +150 -0
- data/lib/tessella/tree.rb +221 -0
- data/lib/tessella/version.rb +5 -0
- data/lib/tessella/wave.rb +353 -0
- data/lib/tessella/width.rb +122 -0
- data/lib/tessella/width_class.rb +77 -0
- data/lib/tessella/width_invisible.rb +66 -0
- data/lib/tessella/width_invisible_high.rb +60 -0
- data/lib/tessella.rb +94 -0
- metadata +226 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
tessella is not on rubygems.org. Install it from a checkout:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
cd tessella
|
|
9
|
+
make install # builds the gem, installs it for this user
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`make uninstall` removes it. Without installing, `exe/tessella` runs
|
|
13
|
+
straight out of the checkout, and `ruby -I lib` finds the library.
|
|
14
|
+
It needs Ruby 3.4 and nothing else.
|
|
15
|
+
|
|
16
|
+
## A first chart
|
|
17
|
+
|
|
18
|
+
Any file of numbers will do. [examples/langs.csv](../examples/langs.csv)
|
|
19
|
+
holds a header and three rows:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
$ cat langs.csv
|
|
23
|
+
lang,loc
|
|
24
|
+
ruby,12
|
|
25
|
+
c,30
|
|
26
|
+
rust,7.5
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
$ tessella chart --kind bar --width 30 langs.csv
|
|
31
|
+
┌ ┐
|
|
32
|
+
ruby ┤██████████▍··············· 12
|
|
33
|
+
c ┤██████████████████████████ 30
|
|
34
|
+
rust ┤██████▌··················· 7.5
|
|
35
|
+
└ ┘
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The same, for a document kept to ASCII:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
$ tessella chart --kind bar --width 30 --ascii langs.csv
|
|
42
|
+
+ +
|
|
43
|
+
ruby |##########................ 12
|
|
44
|
+
c |########################## 30
|
|
45
|
+
rust |#######................... 7.5
|
|
46
|
+
+ +
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## A first diagram
|
|
50
|
+
|
|
51
|
+
Each diagram reads a small text format from a file or stdin:
|
|
52
|
+
|
|
53
|
+
<!-- tessella: allow-mixed -->
|
|
54
|
+
```text
|
|
55
|
+
$ printf 'Client -> Server: GET /\nServer --> Client: 200\n' | tessella seq
|
|
56
|
+
┌─────────┐ ┌─────────┐
|
|
57
|
+
│ Client │ │ Server │
|
|
58
|
+
└────┬────┘ └────┬────┘
|
|
59
|
+
│ │
|
|
60
|
+
│ GET / │
|
|
61
|
+
│───────────►│
|
|
62
|
+
│ │
|
|
63
|
+
│ 200 │
|
|
64
|
+
│◄┄┄┄┄┄┄┄┄┄┄┄│
|
|
65
|
+
│ │
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## From Ruby
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
require "tessella"
|
|
72
|
+
|
|
73
|
+
puts Tessella.bar(%w[ruby c rust], [12, 30, 7.5], width: 30)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Every factory returns an object that prints as the figure, and every
|
|
77
|
+
figure composes with every other: frame it, set it beside another,
|
|
78
|
+
stack, indent or caption it.
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
# frozen_string_literal: true
|
|
82
|
+
|
|
83
|
+
# examples/load.rb: a bar chart and a table side by side, framed and
|
|
84
|
+
# captioned -- composition of any two figures.
|
|
85
|
+
require "tessella"
|
|
86
|
+
|
|
87
|
+
rows = Tessella::Table.parse(File.read("langs.csv"))
|
|
88
|
+
labels = rows.drop(1).map(&:first)
|
|
89
|
+
values = rows.drop(1).map { Integer(it[1], exception: false) || it[1].to_f }
|
|
90
|
+
plot = Tessella.bar(labels, values, width: 20)
|
|
91
|
+
table = Tessella.table(rows, style: :rounded)
|
|
92
|
+
fig = Tessella.beside(plot, table, gap: 2, align: :middle)
|
|
93
|
+
puts Tessella.caption(Tessella.frame(fig, title: "Load"),
|
|
94
|
+
text: "Figure 1: lines of code, in thousands")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
$ ruby load.rb
|
|
99
|
+
┌───────────────────── Load ─────────────────────┐
|
|
100
|
+
│ ╭──────┬─────╮ │
|
|
101
|
+
│ ┌ ┐ │ lang │ loc │ │
|
|
102
|
+
│ ruby ┤██████▍········· 12 ├──────┼─────┤ │
|
|
103
|
+
│ c ┤████████████████ 30 │ ruby │ 12 │ │
|
|
104
|
+
│ rust ┤████············ 7.5 │ c │ 30 │ │
|
|
105
|
+
│ └ ┘ │ rust │ 7.5 │ │
|
|
106
|
+
│ ╰──────┴─────╯ │
|
|
107
|
+
└────────────────────────────────────────────────┘
|
|
108
|
+
|
|
109
|
+
Figure 1: lines of code, in thousands
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Where next
|
|
113
|
+
|
|
114
|
+
- [Charts](charts.md): the eight kinds, their input, options and
|
|
115
|
+
canvases.
|
|
116
|
+
- [Composition](composition.md): assembling figures.
|
|
117
|
+
- [Trees](tree.md), [tables](table.md), [bit fields](bitfield.md),
|
|
118
|
+
[timing diagrams](wave.md), [sequence diagrams](seq.md),
|
|
119
|
+
[pictures](images.md).
|
|
120
|
+
- [Linting](lint.md): checking figures before they ship.
|
|
121
|
+
- [The command](cli.md) and [the library](library.md), for reference.
|
data/doc/glyphs.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Glyph sets and width classes
|
|
2
|
+
|
|
3
|
+
A figure lines up only if every glyph in it takes the columns the
|
|
4
|
+
author counted. Most do, everywhere. Some do in a Western terminal
|
|
5
|
+
and take two columns in one set up for Chinese, Japanese or Korean:
|
|
6
|
+
the box-drawing lines and the shade blocks among them. tessella names
|
|
7
|
+
the glyph sets it draws with, and says for each which of these
|
|
8
|
+
classes its glyphs fall in.
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
Tessella::Width.class_of("▒") # => :A
|
|
12
|
+
Tessella::Glyphs.fetch(:ramp, :blocks) # => a GlyphSet
|
|
13
|
+
Tessella::Glyphs.fetch(:ramp, :blocks).mixed? # => true
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
tessella glyphs [KIND [NAME]]
|
|
18
|
+
tessella glyphs --check GLYPHS
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Width classes
|
|
22
|
+
|
|
23
|
+
Every character has one class, from Unicode 15.1's East Asian Width
|
|
24
|
+
property:
|
|
25
|
+
|
|
26
|
+
| Class | Name | Columns |
|
|
27
|
+
|-------|-----------|--------------------------------------------------|
|
|
28
|
+
| `N` | narrow | one, everywhere |
|
|
29
|
+
| `A` | ambiguous | one in Western terminals, two in CJK-configured ones |
|
|
30
|
+
| `W` | wide | two, everywhere (CJK ideographs, most emoji) |
|
|
31
|
+
|
|
32
|
+
`Tessella::Width.class_of(char)` returns `:N`, `:A` or `:W`. A set
|
|
33
|
+
whose glyphs all share one class keeps its shape in any terminal; a
|
|
34
|
+
set that spans two is *mixed*: right in a Western terminal, but its
|
|
35
|
+
glyphs change width against each other in a CJK one, so a figure
|
|
36
|
+
drawn with it stops lining up there.
|
|
37
|
+
|
|
38
|
+
## The registry
|
|
39
|
+
|
|
40
|
+
`tessella glyphs` lists every named set the library draws with: its
|
|
41
|
+
kind, its name, its classes, the [width targets](targets.md) it is
|
|
42
|
+
safe for, and its glyphs. Every set suits `term`; a set suits `cjk`
|
|
43
|
+
when it holds no ambiguous glyph, and `web` when it holds no wide or
|
|
44
|
+
emoji-capable one. A kind narrows the list to one kind:
|
|
45
|
+
|
|
46
|
+
<!-- tessella: allow-mixed -->
|
|
47
|
+
```text
|
|
48
|
+
$ tessella glyphs border
|
|
49
|
+
border light A term web ┌┐└┘─│
|
|
50
|
+
border heavy A term web ┏┓┗┛━┃
|
|
51
|
+
border double A term web ╔╗╚╝═║
|
|
52
|
+
border rounded A term web ╭╮╰╯─│
|
|
53
|
+
border dashed A term web ┌┐└┘┄┆
|
|
54
|
+
border dashed_fine A term web ┌┐└┘┈┊
|
|
55
|
+
border heavy_dashed A term web ┏┓┗┛┅┇
|
|
56
|
+
border heavy_dashed_fine A term web ┏┓┗┛┉┋
|
|
57
|
+
border double_h A term web ╒╕╘╛═│
|
|
58
|
+
border double_v A term web ╓╖╙╜─║
|
|
59
|
+
border ascii N term cjk web ++++-|
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A kind and a name list one set glyph by glyph, with code points:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
$ tessella glyphs ramp blocks
|
|
66
|
+
░ U+2591 N
|
|
67
|
+
▒ U+2592 A
|
|
68
|
+
▓ U+2593 A
|
|
69
|
+
█ U+2588 A
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The `blocks` ramp is mixed: `░` is narrow, the three others are
|
|
73
|
+
ambiguous. In a CJK terminal its lightest tone is half as wide as the
|
|
74
|
+
others.
|
|
75
|
+
|
|
76
|
+
From Ruby, `Tessella::Glyphs.fetch(kind, name)` returns a `GlyphSet`
|
|
77
|
+
(`kind`, `name`, `glyphs`, `note`, `classes`, `mixed?`, `targets`),
|
|
78
|
+
`Tessella::Glyphs.targets(glyphs)` the targets any glyphs suit, and
|
|
79
|
+
`Tessella::Glyphs.all` every set. An unknown kind or name raises
|
|
80
|
+
`ArgumentError`, listing the names there are. A name may say `-` for
|
|
81
|
+
`_`.
|
|
82
|
+
|
|
83
|
+
`--target T` lists only the sets safe for a target, here the meter
|
|
84
|
+
styles a CJK terminal draws as counted:
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
$ tessella glyphs --target cjk meter
|
|
88
|
+
meter arrow N term cjk web ▸▹
|
|
89
|
+
meter chevron N term cjk web ›
|
|
90
|
+
meter classic N term cjk web =
|
|
91
|
+
meter rectangle N term cjk web ▮▯
|
|
92
|
+
meter wave N term cjk web ~_
|
|
93
|
+
meter blade N term cjk web ▰▱ (patchy font coverage)
|
|
94
|
+
meter button N term cjk web ⦿⦾ (patchy font coverage)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Arrow heads
|
|
98
|
+
|
|
99
|
+
The registry also holds the arrow heads, as kind `head`, with the rule
|
|
100
|
+
for choosing one in their targets and notes:
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
$ tessella glyphs head
|
|
104
|
+
head full N+A term web ►◄▲▼ mixed (▴▾ under cjk)
|
|
105
|
+
head small N term cjk web ▸◂▴▾
|
|
106
|
+
head emoji A term ▶◀ (+U+FE0E on web)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`full` (`►◄▲▼`) is mixed: `► ◄` are narrow, `▲ ▼` ambiguous, so in a
|
|
110
|
+
CJK terminal use `▴ ▾` for up and down; `tessella lint --target cjk`
|
|
111
|
+
says so when it finds `▲` or `▼` on a row that must line up. `small`
|
|
112
|
+
(`▸◂▴▾`) suits every target. `emoji` (`▶◀`) suits the terminal only,
|
|
113
|
+
and needs U+FE0E after it on the web ([width targets](targets.md#web)).
|
|
114
|
+
|
|
115
|
+
## Checking a set of one's own
|
|
116
|
+
|
|
117
|
+
`--check GLYPHS` classifies any glyphs: it prints their class and the
|
|
118
|
+
targets they are safe for, and exits 1 when they are mixed, so a
|
|
119
|
+
build can refuse a glyph set before a figure uses it:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
$ tessella glyphs --check '┌┐└┘─│'
|
|
123
|
+
A
|
|
124
|
+
safe for: term web
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```text
|
|
128
|
+
$ tessella glyphs --check '░▒▓█'
|
|
129
|
+
mixed: N A
|
|
130
|
+
safe for: term web
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
The [lint](lint.md) measures widths with the same tables, so what
|
|
134
|
+
`glyphs` calls narrow, the lint counts as one column.
|
data/doc/images.md
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# Pictures
|
|
2
|
+
|
|
3
|
+
`image` draws a picture as text: a logo for a banner, a plot or a
|
|
4
|
+
screenshot for a terminal, a photograph as a curiosity.
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
Tessella.image(source, style: :ramp, width: 78, ramp: nil, gamma: 1.0,
|
|
8
|
+
threshold: 0.5, dither: :fs, invert: false,
|
|
9
|
+
edge_threshold: 0.3, ascii: false) # => Block
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
tessella image [--style S] [--width N] [--dither M] [--ramp STR|NAME]
|
|
14
|
+
[--gamma F] [--threshold F] [--edge-threshold F]
|
|
15
|
+
[--invert] [--ascii] [--max-width N] [file]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Sources
|
|
19
|
+
|
|
20
|
+
`source` is a `Tessella::Raster`, an IO, or a String of image bytes.
|
|
21
|
+
The format is told by the bytes' own signature, not a file name:
|
|
22
|
+
|
|
23
|
+
- **PGM and PPM** (P2, P3, P5, P6) and **PNG** decode with nothing
|
|
24
|
+
installed, in plain Ruby.
|
|
25
|
+
- **Anything else** (JPEG, GIF, TIFF, WebP, ...) needs the `rmagick`
|
|
26
|
+
gem. tessella uses it when it is installed and never requires it;
|
|
27
|
+
without it the image is refused, naming the way round: convert to
|
|
28
|
+
PGM first.
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
$ printf 'GIF89a' | tessella image 2>&1 | fold -s -w 72 | sed 's/ $//'
|
|
32
|
+
tessella image: not a PNM or PNG image, and the rmagick gem is not
|
|
33
|
+
installed: install it, or convert the image first (magick IMAGE pgm:- |
|
|
34
|
+
tessella image)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
magick photo.jpg pgm:- | tessella image --width 60
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Colour becomes grey by Rec. 601 luma, and transparency is laid over
|
|
42
|
+
black, so a transparent pixel is empty.
|
|
43
|
+
|
|
44
|
+
A `Raster` can also be built in Ruby, from any function: width,
|
|
45
|
+
height, and `width * height` grey values in 0..1, row by row from the
|
|
46
|
+
top, 1.0 the brightest.
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
# frozen_string_literal: true
|
|
50
|
+
|
|
51
|
+
# examples/gradient.rb: a Raster built in Ruby -- no image file --
|
|
52
|
+
# drawn in three styles, side by side.
|
|
53
|
+
require "tessella"
|
|
54
|
+
|
|
55
|
+
w = h = 48
|
|
56
|
+
luma = (0...h).flat_map do |y|
|
|
57
|
+
(0...w).map { |x| Math.hypot(x - 24, y - 24) < 22 ? x / 47.0 : 0 }
|
|
58
|
+
end
|
|
59
|
+
raster = Tessella::Raster.new(width: w, height: h, luma: luma)
|
|
60
|
+
figs = %i[ramp blocks quadrant].map do |style|
|
|
61
|
+
Tessella.caption(Tessella.image(raster, style: style, width: 16),
|
|
62
|
+
text: style.to_s)
|
|
63
|
+
end
|
|
64
|
+
puts Tessella.beside(*figs, gap: 3)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
$ ruby gradient.rb
|
|
69
|
+
.:::-:. ░░░░░░ ▖▖▖▖▖▖
|
|
70
|
+
.:-==++*#*- ░░░▒▒▒▒▓▓▓░ ▝▖▚▐▐▞▛▟▙▄
|
|
71
|
+
.::-==++*##%* ░░░░▒▒▒▒▓▓▓▓▓ ▘▚▝▞▞▞▞▛▟▟▜▙
|
|
72
|
+
..::-==++*##%%- ░░░░▒▒▒▒▓▓▓▓█░ ▘▖▚▝▞▞▞▛▟▜▛█▌
|
|
73
|
+
..::-==++*##%%- ░░░░▒▒▒▒▓▓▓▓█░ ▘▘▝▖▚▚▚▜▞▛▙██▌
|
|
74
|
+
..::-==++*##%# ░░░░▒▒▒▒▓▓▓▓▓ ▗▝▖▞▐▗▚▚▜▜▙▛▛
|
|
75
|
+
.::-==++*##+ ░░░▒▒▒▒▓▓▓▒ ▗▗▝▖▌▙▜▚▙▙▀
|
|
76
|
+
.::--=-- ░░░░▒░░ ▘▘▘▝▝▘▘
|
|
77
|
+
|
|
78
|
+
ramp blocks quadrant
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Styles
|
|
82
|
+
|
|
83
|
+
The style trades tone for resolution. `ramp` and `blocks` give one
|
|
84
|
+
tone per character cell; the others pack several on/off pixels into a
|
|
85
|
+
cell, using dithering for tone; `edges` draws outlines.
|
|
86
|
+
|
|
87
|
+
| Style | Pixels per cell | Glyphs |
|
|
88
|
+
|------------|-----------------|---------------------------------|
|
|
89
|
+
| `ramp` | 1 | `` .:-=+*#%@`` (or `--ramp`) |
|
|
90
|
+
| `blocks` | 1 | ` ░▒▓█` |
|
|
91
|
+
| `half` | 1 × 2 | `▀ ▄ █` |
|
|
92
|
+
| `quadrant` | 2 × 2 | `▘▝▀▖▌▞▛▗▚▐▜▄▙▟█` |
|
|
93
|
+
| `sextant` | 2 × 3 | Unicode 13 sextants `🬀`…`🬻` |
|
|
94
|
+
| `braille` | 2 × 4 | braille patterns |
|
|
95
|
+
| `octant` | 2 × 4 | Unicode 16 octants (see below) |
|
|
96
|
+
| `petscii` | 4 × 8 tones | about forty shapes (see below) |
|
|
97
|
+
| `edges` | 1 | `─ │ ╱ ╲`, Sobel contours |
|
|
98
|
+
|
|
99
|
+
`disc.pgm` is a 64 × 64 grey disc, brightest at its centre:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
$ tessella image --width 24 disc.pgm
|
|
103
|
+
|
|
104
|
+
...::...
|
|
105
|
+
.::--====---:.
|
|
106
|
+
:--=+++**+++==-:.
|
|
107
|
+
:-==+**####**++=-:.
|
|
108
|
+
.:-=+**#%%%%##*+==:.
|
|
109
|
+
.:-=+*##%%%%##*+==:.
|
|
110
|
+
.:-=++**#####*++=-:.
|
|
111
|
+
.:-==+++***++==-:.
|
|
112
|
+
.:--======--:.
|
|
113
|
+
..::::::..
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```text
|
|
117
|
+
$ tessella image --style blocks --width 24 disc.pgm
|
|
118
|
+
|
|
119
|
+
░░░░░
|
|
120
|
+
░░░▒▒▒▒▒▒░░░
|
|
121
|
+
░░▒▒▒▒▒▒▒▒▒▒▒▒░░
|
|
122
|
+
░░▒▒▒▓▓▓▓▓▓▓▓▒▒▒░░
|
|
123
|
+
░░▒▒▓▓▓▓██▓▓▓▓▒▒▒░░
|
|
124
|
+
░░▒▒▓▓▓▓███▓▓▓▒▒▒░░
|
|
125
|
+
░░▒▒▒▓▓▓▓▓▓▓▓▒▒▒░░
|
|
126
|
+
░░▒▒▒▒▒▓▓▒▒▒▒▒░░
|
|
127
|
+
░░░▒▒▒▒▒▒░░░░
|
|
128
|
+
░░░░░░░░
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The braille renders on this page line up only in a terminal: on
|
|
132
|
+
GitHub braille is drawn about 1.2 columns wide
|
|
133
|
+
([width targets](targets.md#web)).
|
|
134
|
+
|
|
135
|
+
```text
|
|
136
|
+
$ tessella image --style braille --width 24 disc.pgm
|
|
137
|
+
|
|
138
|
+
⢀ ⢄⠠⠠⠠⢀⢀
|
|
139
|
+
⠄⢌⢢⢡⢱⢸⢘⢜⠔⡔⡨⠠⡀
|
|
140
|
+
⠠⠡⡣⡱⡱⡱⣕⢵⣣⡳⣕⢕⡜⡌⡢⢁
|
|
141
|
+
⠠⡑⢕⢱⡱⣝⢮⣗⣯⢾⣺⢮⡳⣕⢕⢜⠄⢅
|
|
142
|
+
⢅⠪⡪⣪⢺⣺⢽⣾⣻⣿⣽⢯⣞⢮⡣⡣⡃⢅⠐
|
|
143
|
+
⠢⡑⢕⢕⣝⢾⢽⡾⣿⢿⣽⣻⣺⢵⡹⡸⡨⢂⠂
|
|
144
|
+
⡁⠪⡸⡸⡸⣕⢯⢯⣻⢽⣳⢽⡪⡇⡇⡇⡪⠐
|
|
145
|
+
⠡⢂⢕⢱⠱⡍⣗⢵⢫⢎⢧⢣⢣⠣⢊⠐⠈
|
|
146
|
+
⠂⠐⠌⡊⡪⠢⠣⡣⢃⠇⢕⠐⠅⠂
|
|
147
|
+
⠈ ⠂⠂⠡⠑⠨⠠⠑ ⠁
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
$ tessella image --style edges --width 24 disc.pgm
|
|
152
|
+
|
|
153
|
+
────────
|
|
154
|
+
╱────────────╲
|
|
155
|
+
╱╱╱╱────────╲╲╲╲
|
|
156
|
+
╲
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
╱
|
|
160
|
+
╲╲╲╲────────╱╱╱╱
|
|
161
|
+
╲────────────╱
|
|
162
|
+
──────────
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
[examples/gallery.sh](../examples/gallery.sh) draws one picture in
|
|
166
|
+
four styles side by side ([output](cli.md#several-figures-one-result)).
|
|
167
|
+
|
|
168
|
+
The sextant glyphs are recent (Unicode 13, 2020): check that the
|
|
169
|
+
reader's font has them before using `sextant`.
|
|
170
|
+
|
|
171
|
+
`octant` packs 2 × 4 pixels into a cell, as braille does, but as
|
|
172
|
+
solid blocks rather than dots, so a picture keeps its tone where
|
|
173
|
+
braille turns grey. Its glyphs are Unicode 16 (2024), and only the
|
|
174
|
+
newest terminals draw them (Ghostty and its like); elsewhere each
|
|
175
|
+
cell shows as a box or a blank. That is why this page shows no
|
|
176
|
+
octant render: try `tessella image --style octant disc.pgm` in your
|
|
177
|
+
own terminal.
|
|
178
|
+
|
|
179
|
+
`petscii` draws as the Commodore character set did: each cell's 4 × 8
|
|
180
|
+
tones are matched to the glyph of least error among about forty
|
|
181
|
+
shapes (halves, quadrants, `▏▕▔▁`, three-quarter blocks `▊ ▆` and
|
|
182
|
+
their kin `▂ 🮊 🮂 🮅`, the triangles `◢◣◤◥`, `╱╲╳`, the shades `░▒▓`,
|
|
183
|
+
lines, corners and tees, `● ○`). `🮊 🮂 🮅` are from Unicode 13's
|
|
184
|
+
Legacy Computing block, so the font caveat on sextants holds here
|
|
185
|
+
too. A soft picture comes out in shades:
|
|
186
|
+
|
|
187
|
+
```text
|
|
188
|
+
$ tessella image --style petscii --width 24 disc.pgm
|
|
189
|
+
|
|
190
|
+
░░░░░░
|
|
191
|
+
░░░▒▒▒▒▒▒░░░
|
|
192
|
+
░░▒▒▒▒▒▒▒▒▒▒▒▒░░
|
|
193
|
+
░░▒▒▒▓▓▓▓▓▓▓▓▒▒▒░░
|
|
194
|
+
░░▒▒▓▓▓▓██▓▓▓▓▒▒▒░░
|
|
195
|
+
░░▒▒▓▓▓▓██▓▓▓▓▒▒▒░░
|
|
196
|
+
░░▒▒▒▓▓▓▓▓▓▓▓▒▒▒░░
|
|
197
|
+
░░▒▒▒▒▒▒▒▒▒▒▒▒░░
|
|
198
|
+
░░░▒▒▒▒▒▒░░░░
|
|
199
|
+
░░░░░░
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Hard edges are where it shows its shapes. `shapes.pgm` is a ring and
|
|
203
|
+
a wedge in black and white:
|
|
204
|
+
|
|
205
|
+
```text
|
|
206
|
+
$ tessella image --style petscii --width 24 shapes.pgm
|
|
207
|
+
▁▁▁▁ 🮊███████◤
|
|
208
|
+
▗▄██████▆▖ 🮊██████▘
|
|
209
|
+
◢███▀▀🮂▀▜██▙ 🮊█████▘
|
|
210
|
+
▕██▊ ▐██▌🮊████▘
|
|
211
|
+
▕██▊ ▐██▌🮊███▘
|
|
212
|
+
▜██▙▄▂▂▂▟██▛ 🮊█▛
|
|
213
|
+
▝🮅██████🮅▀ 🮊▛
|
|
214
|
+
🮂🮂🮂🮂 ╱
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`petscii` takes no dithering, so `--threshold` and `--dither` do not
|
|
218
|
+
apply to it.
|
|
219
|
+
|
|
220
|
+
## Tone
|
|
221
|
+
|
|
222
|
+
- `--width N`: the picture's width in cells, 78 by default; the
|
|
223
|
+
height follows from the image's aspect, a cell being about twice as
|
|
224
|
+
tall as wide.
|
|
225
|
+
- `--gamma F`: every tone t in 0..1 is drawn as t to the power F;
|
|
226
|
+
above 1 pushes the mid-tones toward empty, below 1 toward full.
|
|
227
|
+
- `--invert`: tones are drawn as 1 − t. By default a bright pixel
|
|
228
|
+
gets a dense glyph, which suits light text on a dark terminal;
|
|
229
|
+
invert for dark text on a light page.
|
|
230
|
+
- `--ramp STR`: the tone glyphs for `ramp`, from empty to full, or
|
|
231
|
+
the name of a ramp in the [registry](glyphs.md#the-registry):
|
|
232
|
+
`ramp` (the default), `blocks`, `long` (70 ASCII glyphs, for finer
|
|
233
|
+
tone) or `hatch`. A name wins over the same letters taken as
|
|
234
|
+
glyphs.
|
|
235
|
+
- `--threshold F`: the tone at which a pixel is on, in the pixel
|
|
236
|
+
styles (0.5 by default).
|
|
237
|
+
- `--dither M`: how the pixel styles render tone: `fs`
|
|
238
|
+
(Floyd-Steinberg, the default), `atkinson`, `bayer` (ordered), or
|
|
239
|
+
`none` (a plain threshold).
|
|
240
|
+
- `--edge-threshold F`: the gradient strength an `edges` outline
|
|
241
|
+
needs (0.3 by default).
|
|
242
|
+
|
|
243
|
+
```text
|
|
244
|
+
$ tessella image --style braille --dither none --width 24 disc.pgm
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
⢀⣤⣾⣿⣿⣿⣿⣷⣦⡀
|
|
249
|
+
⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦
|
|
250
|
+
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆
|
|
251
|
+
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇
|
|
252
|
+
⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟
|
|
253
|
+
⠈⠻⣿⣿⣿⣿⣿⣿⠿⠋
|
|
254
|
+
⠈⠉⠉⠁
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
```text
|
|
258
|
+
$ tessella image --ramp ' .oO@' --width 20 disc.pgm
|
|
259
|
+
|
|
260
|
+
.......
|
|
261
|
+
..oooooooo..
|
|
262
|
+
..oooOOOOOooo.
|
|
263
|
+
..ooOOO@@OOOoo..
|
|
264
|
+
..ooOOO@@OOOoo..
|
|
265
|
+
.oooOOOOOOooo..
|
|
266
|
+
..oooooooo..
|
|
267
|
+
........
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
```text
|
|
271
|
+
$ tessella image --ramp long --width 30 disc.pgm
|
|
272
|
+
|
|
273
|
+
.`^",:,"`'
|
|
274
|
+
',!<_?][[]?-~i;`
|
|
275
|
+
':<]{(/fjrrjft\)}_l^
|
|
276
|
+
^i-1/rucYUJJJYXvxf(]~I.
|
|
277
|
+
^i?1tnzJQOmqqwZ0CYvj([~;.
|
|
278
|
+
.I+}\xzJ0wdhooakpZLYut)?>^
|
|
279
|
+
'!-1/nYQmdh#8%WokqOJcj|[~:
|
|
280
|
+
.l_{\xXCOqba##*hdmQUvf(]<"
|
|
281
|
+
">])fuXC0mqddpwZQUcr|}_l.
|
|
282
|
+
:<](fxcXJCLQLCUzur\}_!`
|
|
283
|
+
^l_}(/frxnnnxjt|1?>:.
|
|
284
|
+
^l<_]}{11{}[-+i,.
|
|
285
|
+
.`,;Il!l;:^'
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
```text
|
|
289
|
+
$ tessella image --ramp hatch --width 20 disc.pgm
|
|
290
|
+
|
|
291
|
+
·······
|
|
292
|
+
··∘∘∘∘∘∘∘∘··
|
|
293
|
+
··∘∘∘╱╱╱╱╱∘∘∘·
|
|
294
|
+
··∘∘╱╱╱╳╳╱╱╱∘∘··
|
|
295
|
+
··∘∘╱╱╱╳╳╱╱╱∘∘··
|
|
296
|
+
·∘∘∘╱╱╱╱╱╱∘∘∘··
|
|
297
|
+
··∘∘∘∘∘∘∘∘··
|
|
298
|
+
········
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## ASCII
|
|
302
|
+
|
|
303
|
+
`--ascii` allows only the `ramp` style, with an ASCII ramp:
|
|
304
|
+
|
|
305
|
+
```text
|
|
306
|
+
$ tessella image --style blocks --ascii disc.pgm
|
|
307
|
+
tessella image: ascii: true allows only style: :ramp, not :blocks
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Errors
|
|
311
|
+
|
|
312
|
+
A picture that would render blank — `edges` on a flat image, a
|
|
313
|
+
threshold above every tone — is refused, naming what to change,
|
|
314
|
+
rather than printed as nothing (exit 1):
|
|
315
|
+
|
|
316
|
+
```text
|
|
317
|
+
$ tessella image --style braille --threshold 1 --dither none disc.pgm 2>&1 | \
|
|
318
|
+
fold -s -w 72 | sed 's/ $//'
|
|
319
|
+
tessella image: the picture renders blank at style braille: lower
|
|
320
|
+
threshold, or try invert or another style
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
A malformed or truncated image is bad input (exit 1); an unknown
|
|
324
|
+
style or dither, or a style that `--ascii` does not allow, is a usage
|
|
325
|
+
error (exit 2).
|