cheap-impression-presenter 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76030894a0e7c54a9977b6fc6f65e11d4c516191192ca10eda508ad46cff45e6
4
- data.tar.gz: 1014aa89eb276c6223f0f1529c8578e3b454d4b2d73ae9929786d2910b3e0a6a
3
+ metadata.gz: d17cd3acd2e14f93234d32a413c6356104901a2a9d10a224c9fb191d2dc0f82a
4
+ data.tar.gz: 2d2ded7f61c5b0bb2e895097971fd93d321a8253f9d0718dfca0fbdad758a949
5
5
  SHA512:
6
- metadata.gz: 0b788a032d8441f975d781d8cd2a9787fb2ffe12727c712af5fe837cc8f9ee37244425a75494a52b7bbc31f85b4c777c3cb24510fb644f270772c7fee9519da3
7
- data.tar.gz: de89b182400f26184b2dfe5a3c74b3d624a34b0055faf67b08aa10a5f0c3de0c51fcd035872563072ed51e47780f7b81eef088255ee2d1be7cd26dc48e02cc92
6
+ metadata.gz: 623c58715bc814d8385432867cbfd6aec6f5ef676a9049190a03aca71fa2e1a3f58a23c5cfe305ff2992921f4b5daf801aaecf4a88b82998d58e926d474f216a
7
+ data.tar.gz: '0139f72003c626a3b9e4eb17f9eafd93baafeccd193c32951d26e813ec0b1a8026cd8a34e2cf7e825ac76b2d790b3fbb4a2b639b345719191dc91e099e7759b8'
data/INSTALL.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ```bash
2
- gem install chimp
2
+ gem install cheap-impression-presenter
3
3
  ```
4
4
 
5
5
  It wont work in a windows terminal for now.
@@ -10,4 +10,8 @@ You can utilize 'img2sixel' if you want to display graphics on your terminal
10
10
  (chimp has a sixel plugin).
11
11
 
12
12
  You can utilize 'inkscape' if you want to display svgs as sixel graphics on
13
- your terminal (chimp has a sixel plugin).
13
+ your terminal, or include them in the PDF output (chimp has a sixel plugin).
14
+
15
+ The PDF output (chimp -o pdf) uses the DejaVu Sans Mono font if it is
16
+ installed. Otherwise Courier is used, which lacks some characters (e.g. box
17
+ drawing).
data/README.md CHANGED
@@ -12,28 +12,51 @@ Make a cheap impression. Present via terminal.
12
12
 
13
13
  ```gem install cheap-impression-presenter```
14
14
 
15
+ The ruby gems it needs (tty-screen, pastel, hexapdf) are installed with it. Some
16
+ features need external programs, which have to be installed separately (all are
17
+ available in the usual linux distributions and homebrew):
18
+
19
+ * ``figlet``: for the figlet plugin.
20
+ * ``img2sixel`` (from libsixel): for the sixel plugin, i.e. pictures in the terminal.
21
+ Your terminal also needs sixel support.
22
+ * ``inkscape``: for svg pictures, in the terminal as well as in the PDF output.
23
+ * DejaVu Sans Mono font: for the PDF output. If it is not installed, Courier is
24
+ used, which lacks some characters (e.g. box drawing).
25
+
15
26
  It has a simple syntax:
16
27
 
17
28
  #what: the title of presentation
29
+ #size: the number of lines to zoom to (default 26, see below)
30
+ #theme: the PDF colors, e.g. background=#fdf6e3 text=#657b83 (see below)
31
+ #template: content drawn before anything else on the slide (e.g. ```figlet,right LOGO)
32
+ #template:
33
+ content drawn before anything else on the slide
34
+ #end
18
35
 
19
36
  --- splits slides
20
37
 
21
38
  +++ slides parts to appear
22
39
 
23
40
  !!Bold Text!!
24
- ``Red Text``
41
+ ''Red Text''
25
42
  %%Blue Text%%
26
43
  ~~Center Text
44
+ ~~Center Text with !!Bold!!, ''Red'' and %%Blue%%
45
+
46
+ Plugins can not be used in centered lines, they have
47
+ their own center parameter.
27
48
 
28
49
  Everything else is in plugins. The following plugins are existing for now:
29
50
 
30
51
  * Sixel: display graphics in the terminal: `` ```sixel,20 name.png``.
31
52
  * The parameter 20 denotes the size in characters.
32
53
  * You can add right or center as an additional parameter to align the picture.
33
- * If the picture is right aligned, text is added on the left of the picture instead of beneath.
54
+ * If the picture is right aligned and float is added as an additional parameter, text is added on the left of the picture instead of beneath.
55
+ * You can add animate as an additional parameter to return the cursor to where the picture started. Text that follows is then added on top of the picture instead of beneath. Combined with +++ the next picture replaces the current one, which allows for simple animations (see examples/sixel_svg.2.txt).
34
56
  * Figlet: display fancy ascii text: `` ```figlet FANCY_TEXT``.
35
57
  * You can add right or center as a parameter to align the fancy text.
36
- * If the fancy text is right aligned, normal text is added on the left of the fancy text instead of beneath.
58
+ * If the fancy text is right aligned, normal text is added on the left of the fancy text instead of beneath.
59
+ * You can add bold as an additional parameter to display the fancy text in bold (in the PDF in the bold color).
37
60
  * Line Numbers: display line numbers beforce any text (mostly used for code :-).
38
61
  ~~~text
39
62
  ```lnums
@@ -43,6 +66,10 @@ Line
43
66
  ```
44
67
  ~~~
45
68
 
69
+ Templates should be positioned carefully (e.g. right aligned figlet or sixel) to
70
+ not collide with the content. All header lines (#what, #size, #theme, #template)
71
+ have to be at the very beginning of the presentation.
72
+
46
73
  Run the presentation with:
47
74
 
48
75
  ```bash
@@ -50,17 +77,24 @@ chimp mypresentation.txt
50
77
  ```
51
78
 
52
79
  While presenting you can use the usual button for navigating. In addition, you
53
- can use ``q`` to quit the presentation, and ``r`` to refresh the screen. I suggest zooming
54
- the terminal until about 30 lines are visible. To make this easy a lightgray ``^`` character is
55
- visible in the rightmost column of the terminal. Zoom until the ``^`` character is no longer visible for
56
- the most audience friendly results.
80
+ can use ``q`` to quit the presentation, and ``r`` to refresh the screen. I
81
+ suggest zooming the terminal until about 26 lines are visible. To make this
82
+ easy a ``^`` character is visible at the right end of the footer line. Zoom
83
+ until the ``^`` character is no longer visible for the most audience friendly
84
+ results. The ``^`` is shown as long as more than 26 lines are visible, use
85
+ ``#size:`` directly after ``#what:`` to change this number.
57
86
 
58
87
  Check out the examples.
59
88
 
89
+ How does it look?
90
+ =================
91
+
92
+ ![CHIMP Screenshot](chimp_screenshot.png)
93
+
60
94
  History & Thanks
61
95
  ================
62
96
 
63
- Amy Hoy allowed me to use the monkey ascii art she created for the CHIMP all the way back in 2009. The
97
+ Amy Hoy all the way back in 2009 allowed me to use her monkey ascii art for the CHIMP. The
64
98
  CHIMP was written as an icebreaker for the curucamp, which was a ruby
65
99
  un-conference in 2009 in Vienna. We had fun, and many cool people from the
66
100
  community attended.
@@ -74,11 +108,31 @@ into graphics anyway, which I create with dedicated tools.
74
108
  Drop me a note if you want to be a contributor, or send me merge request if you
75
109
  have created some plugins. Creating plugins is very simple :-)
76
110
 
77
- Missing Features
78
- ================
111
+ PDF Output
112
+ ==========
113
+
114
+ To send the presentation to people in one file, write it to a PDF:
115
+
116
+ ```bash
117
+ chimp -o pdf mypresentation.txt
118
+ ```
119
+
120
+ This writes mypresentation.pdf to the directory you are currently in.
121
+
122
+ By default the slides are 26 lines high, as if you zoomed the terminal. It
123
+ respects the size header. Pictures are embedded as vector graphics for SVGs or
124
+ as images for everything else.
125
+
126
+ The default colors are solarized light. Change them with a ``#theme:`` line in
127
+ the header, holding KEY=#RRGGBB pairs for background, text, bold, red, blue and
128
+ footer. Colors you leave out keep their default. Solarized dark for example:
129
+
130
+ #theme: background=#002b36 text=#839496 bold=#93a1a1 red=#dc322f blue=#268bd2 footer=#586e75
131
+
132
+ examples/chimp.txt (light) and examples/markus.txt (dark) show both.
79
133
 
80
- Presenting is nice. A PDF output mode would be useful ... to send the presentation
81
- to people in one file.
134
+ A ``#theme:`` line can also be put on any slide (on a line of its own). It
135
+ overrides the given colors from there on, for this and all following slides.
82
136
 
83
137
  LICENSE
84
138
  =======
data/chimp.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "cheap-impression-presenter"
3
- s.version = "1.0.1"
3
+ s.version = "1.1.1"
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.license = "GPL-3.0-or-later"
6
6
  s.summary = "CHIMP - Make a cheap impression. Present via terminal."
@@ -22,4 +22,5 @@ Gem::Specification.new do |s|
22
22
 
23
23
  s.add_runtime_dependency 'tty-screen', '~> 0.8'
24
24
  s.add_runtime_dependency 'pastel', '~> 0.8'
25
+ s.add_runtime_dependency 'hexapdf', '~> 1.0'
25
26
  end
@@ -10,6 +10,10 @@ module Chimp
10
10
 
11
11
  P_SLIDES = Pattern.new("P_SLIDES", /---+.*?(\z|\n)|\A/, /^(?=---+)|\z/)
12
12
  P_WHAT = Pattern.new("P_WHAT", /^#\s*what:\s*/, /[\t ]*(\n|\z)/)
13
+ P_SIZE = Pattern.new("P_SIZE", /^#\s*size:\s*/, /[\t ]*(\n|\z)/)
14
+ P_THEME = Pattern.new("P_THEME", /^#\s*theme:\s*/, /[\t ]*(\n|\z)/)
15
+ P_TEMPLATES = Pattern.new("P_TEMPLATE", /^#\s*template:[\t ]*\n/, /^#\s*end[\t ]*(\n|\z)/)
16
+ P_TEMPLATE = Pattern.new("P_TEMPLATE", /^#\s*template:[\t ]*/, /[\t ]*(\n|\z)/)
13
17
  P_INCREMENTAL = Pattern.new("P_INCREMENTAL", /\+\+\++.*?(\z|\n)|\A/, /^(?=\+\+\++)|\z/)
14
18
  P_RANGE = Pattern.new("P_RANGE", /^```([a-z0-9_]+,)*[a-z0-9_]+[\t ]*/, /^```[\t ]*(\n|\z)/)
15
19
  P_INCLUDE = Pattern.new("P_INCLUDE", /^```([a-z0-9_]+,)*[a-z0-9_]+[\t ]+/, /[\t ]*(\n|\z)/)
@@ -25,16 +29,13 @@ module Chimp
25
29
  #### G + patternname is selected to look up nested patterns ####
26
30
  #### If G + patternname not exists, [] is assumend ####
27
31
 
28
- ROOT = [ P_WHAT, P_SLIDES ]
32
+ ROOT = [ P_WHAT, P_SIZE, P_THEME, P_TEMPLATES, P_TEMPLATE, P_SLIDES ]
29
33
  GP_SLIDES = [ P_INCREMENTAL ]
30
- GP_INCREMENTAL = [ P_CENTER, P_RANGE, P_INCLUDE, P_STRONGRED, P_STRONGBLUE, P_STRONG, P_RED, P_BLUE ]
34
+ GP_INCREMENTAL = [ P_THEME, P_CENTER, P_RANGE, P_INCLUDE, P_STRONGRED, P_STRONGBLUE, P_STRONG, P_RED, P_BLUE ]
35
+ GP_TEMPLATE = GP_INCREMENTAL
36
+ GP_CENTER = [ P_STRONGRED, P_STRONGBLUE, P_STRONG, P_RED, P_BLUE ]
31
37
 
32
38
  ### Optional functions called when a pattern occurs (m + patternname) ####
33
- def mP_CENTER(ts,ti,te)
34
- @tree.last.data = ti.length
35
- ti
36
- end
37
-
38
39
  def mP_INCLUDE(ts,ti,te)
39
40
  @tree.last.data = {}
40
41
  @tree.last.data[:name] = ts[3..-1].strip.split(',')[0]
@@ -54,6 +55,14 @@ module Chimp
54
55
  @tree.last.data = ti
55
56
  ''
56
57
  end
58
+ def mP_SIZE(ts,ti,te)
59
+ @tree.last.data = ti.to_i
60
+ ''
61
+ end
62
+ def mP_THEME(ts,ti,te)
63
+ @tree.last.data = ti
64
+ ''
65
+ end
57
66
  end
58
67
 
59
68
  end
data/lib/ChimpParser.rb CHANGED
@@ -30,6 +30,8 @@ module Chimp
30
30
  class TagContentSkipEvent < Exception; end
31
31
 
32
32
  class Output
33
+ SIZE = 26 # default number of lines of a slide (including the footer), #size: overrides
34
+
33
35
  def finish_prepare
34
36
  end
35
37
  def finish_output
@@ -38,6 +40,35 @@ module Chimp
38
40
  def string(data)
39
41
  data
40
42
  end
43
+
44
+ #### calls the output method for element i of the tree, raises NameError if there is none
45
+ def dispatch(tree,i)
46
+ c = tree[i]
47
+ met, data = case c
48
+ when String then [method("string"), c]
49
+ when OpenTag then [method("mO" + c.ttype), c.data]
50
+ when CloseTag then [method("mC" + c.ttype), tree[c.open].data]
51
+ end
52
+ case met.arity
53
+ when 0; met.call
54
+ when 1; met.call(data)
55
+ when 2; met.call(c,tree)
56
+ when 3; met.call(c,tree,i)
57
+ when 4; met.call(c,tree,i,data)
58
+ else
59
+ ""
60
+ end
61
+ end
62
+
63
+ #### outputs a part of the tree again (e.g. a template for every slide)
64
+ def replay(tree,from,to)
65
+ from.upto(to) do |i|
66
+ begin
67
+ dispatch(tree,i)
68
+ rescue NameError
69
+ end
70
+ end
71
+ end
41
72
  end
42
73
 
43
74
  class Grammar
@@ -83,26 +114,7 @@ module Chimp
83
114
  while @tree.length > i
84
115
  c = @tree[i]
85
116
  begin
86
- met = case
87
- when c.class == String
88
- data = c
89
- output.method("string")
90
- when c.class == OpenTag
91
- data = c.data
92
- output.method("mO" + c.ttype)
93
- when c.class == CloseTag
94
- data = @tree[c.open].data
95
- output.method("mC" + c.ttype)
96
- end
97
- out << case met.arity
98
- when 0; met.call
99
- when 1; met.call(data)
100
- when 2; met.call(c,@tree)
101
- when 3; met.call(c,@tree,i)
102
- when 4; met.call(c,@tree,i,data)
103
- else
104
- ""
105
- end.to_s
117
+ out << output.dispatch(@tree,i).to_s
106
118
 
107
119
  i += 1
108
120
  rescue NameError => e
data/lib/ChimpPlugin.rb CHANGED
@@ -2,6 +2,8 @@ module Chimp
2
2
  module Plugin
3
3
  class Base
4
4
  def initialize; end
5
+ def prepare(what,parameters,additional); end
6
+ def process(what,parameters,screen,additional); end
5
7
  end
6
8
  end
7
9
  end
data/lib/output/pdf.rb ADDED
@@ -0,0 +1,227 @@
1
+ require 'hexapdf'
2
+ require 'fileutils'
3
+ require_relative 'screen'
4
+
5
+ module Chimp
6
+ class Parser
7
+
8
+ class Pdf < Screen
9
+ #### solarized light, #theme: in the header overrides single colors, e.g.
10
+ #### #theme: background=#002b36 text=#839496 bold=#93a1a1 footer=#586e75
11
+ THEME = {
12
+ :background => 'fdf6e3', :text => '657b83', :bold => '586e75',
13
+ :red => 'dc322f', :blue => '268bd2', :footer => '93a1a1'
14
+ }
15
+ FONTS = [
16
+ %w(/usr/share/fonts/dejavu-sans-mono-fonts/DejaVuSansMono.ttf /usr/share/fonts/dejavu-sans-mono-fonts/DejaVuSansMono-Bold.ttf),
17
+ %w(/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf /usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf),
18
+ %w(/usr/share/fonts/TTF/DejaVuSansMono.ttf /usr/share/fonts/TTF/DejaVuSansMono-Bold.ttf),
19
+ %w(/Library/Fonts/DejaVuSansMono.ttf /Library/Fonts/DejaVuSansMono-Bold.ttf)
20
+ ]
21
+ WIDTH = 960 # 16:9 page in points
22
+ HEIGHT = 540
23
+ MARGIN = 20
24
+
25
+ #### a virtual terminal that collects what is printed into a character grid {{{
26
+ class Terminal
27
+ attr_reader :columns, :lines, :images
28
+
29
+ def initialize(columns,lines,pdf)
30
+ @columns = columns
31
+ @lines = lines
32
+ @pdf = pdf
33
+ clear
34
+ end
35
+
36
+ def clear
37
+ @grid = Array.new(@lines) { Array.new(@columns) }
38
+ @images = []
39
+ @x = @y = 1
40
+ @bold = false
41
+ @color = nil
42
+ self
43
+ end
44
+
45
+ def set_pos(x,y)
46
+ @x = x < 1 ? 1 : x
47
+ @y = y < 1 ? 1 : y
48
+ self
49
+ end
50
+ def get_pos
51
+ [@x, @y]
52
+ end
53
+
54
+ def print(a)
55
+ a.to_s.scan(/\e\[([\d;]*)m|(\r?\n)|(.)/m) do |sgr,nl,char|
56
+ if sgr
57
+ sgr.split(';').map(&:to_i).each do |code|
58
+ case code
59
+ when 0 then @bold = false; @color = nil
60
+ when 1 then @bold = true
61
+ when 31 then @color = :red
62
+ when 34 then @color = :blue
63
+ end
64
+ end
65
+ elsif nl
66
+ @x = 1
67
+ @y += 1
68
+ else
69
+ if @x > @columns # deferred autowrap, like a real terminal
70
+ @x = 1
71
+ @y += 1
72
+ end
73
+ @grid[@y-1][@x-1] = [char, @bold, @color] if @y <= @lines
74
+ @x += 1
75
+ end
76
+ end
77
+ end
78
+
79
+ ### the image is placed at the cursor, the cursor moves below it
80
+ def image(what,width,fname)
81
+ img, rows = @pdf.load_image(what,width,fname)
82
+ return print("Image #{what} not found.\n") if img.nil?
83
+ @images << { :file => img, :x => @x, :y => @y, :width => width }
84
+ @y += rows
85
+ end
86
+
87
+ ### yields runs of equally formatted text: x, y, text, bold, color
88
+ def each_run
89
+ @grid.each_with_index do |row,y|
90
+ x = 0
91
+ while x < row.length
92
+ if row[x].nil? || row[x][0] == ' '
93
+ x += 1
94
+ next
95
+ end
96
+ start = x
97
+ text = ''
98
+ while x < row.length && row[x] && row[x][1..2] == row[start][1..2]
99
+ text << row[x][0]
100
+ x += 1
101
+ end
102
+ yield start + 1, y + 1, text.rstrip, row[start][1], row[start][2]
103
+ end
104
+ end
105
+ end
106
+ end #}}}
107
+
108
+ #### the pdf is written next to the presentation: talk.txt -> talk.pdf
109
+ def initialize(fname) #{{{
110
+ @scounter = @ccounter = 0
111
+ @what = ''
112
+ @size = SIZE
113
+ @last = 0
114
+ @skip = -1
115
+ @templates = []
116
+
117
+ @fname = File.basename(fname,'.*') + '.pdf'
118
+ @theme = THEME.dup
119
+ @doc = HexaPDF::Document.new
120
+ @doc.config['font.on_missing_glyph'] = proc { |char,font| font.decode_utf8('?').first }
121
+ regular, bold = FONTS.find { |f| f.all? { |e| File.exist?(e) } }
122
+ @font = @doc.fonts.add(regular || 'Courier')
123
+ @bold = @doc.fonts.add(bold || 'Courier', **(bold ? {} : { :variant => :bold }))
124
+ @page = nil
125
+ end #}}}
126
+
127
+ #### the slide always has @size lines, the last two of which are the footer
128
+ def finish_prepare #{{{
129
+ @rowh = (HEIGHT - 2 * MARGIN).to_f / @size
130
+ @fsize = @rowh / 1.2
131
+ @cw = @font.decode_utf8('M').first.width * @fsize / 1000.0
132
+ columns = ((WIDTH - 2 * MARGIN) / @cw).floor
133
+ @left = (WIDTH - columns * @cw) / 2.0
134
+ @screen = Terminal.new(columns, @size - 2, self)
135
+ end #}}}
136
+
137
+ def finish_output #{{{
138
+ @doc.write(@fname, :optimize => true)
139
+ nil
140
+ end #}}}
141
+
142
+ #### a #theme: in a slide overrides colors from this point on
143
+ def mOP_THEME(data) #{{{
144
+ data.split.each do |pair|
145
+ key, color = pair.split('=',2)
146
+ unless THEME.key?(key.to_sym) && color =~ /\A#?(\h{6})\z/
147
+ abort "Invalid #theme: entry '#{pair}', use KEY=#RRGGBB with KEY one of: #{THEME.keys.join(', ')}"
148
+ end
149
+ @theme[key.to_sym] = $1
150
+ end
151
+ end #}}}
152
+
153
+ def mOP_SLIDES(c,tree)
154
+ @page = c.userdata
155
+ @screen.clear
156
+ template tree
157
+ end
158
+ #### every +++ step (animation frame) becomes a page of its own
159
+ def mOP_INCREMENTAL(c,tree); end
160
+ def mCP_INCREMENTAL(c,tree,i)
161
+ render
162
+ end
163
+
164
+ #### plugins only need to prepare things for the terminal
165
+ def mPP_INCLUDE(data); end
166
+ def mPP_RANGE(data); end
167
+
168
+ #### an image in the pdf, svgs are converted to pdf with inkscape (cached)
169
+ def load_image(what,width,fname) #{{{
170
+ src = File.join(File.dirname(fname),what)
171
+ return unless File.exist?(src)
172
+ if what =~ /\.svg$/
173
+ cache = File.join(File.dirname(fname),'.' + File.basename(fname,'.*'),"#{what.gsub('/','_')}.pdf")
174
+ unless File.exist?(cache) && File.mtime(cache) >= File.mtime(src)
175
+ FileUtils.mkdir_p(File.dirname(cache))
176
+ system 'inkscape', '-D', '--export-type=pdf', src, '-o', cache, :err => File::NULL
177
+ end
178
+ src = cache
179
+ end
180
+ img = @doc.images.add(src)
181
+ w, h = img.is_a?(HexaPDF::Type::Form) ? [img.box.width, img.box.height] : [img.width, img.height]
182
+ [img, (width * @cw * h / w / @rowh).ceil]
183
+ end #}}}
184
+
185
+ def render #{{{
186
+ return if @page.nil?
187
+ canvas = @doc.pages.add([0, 0, WIDTH, HEIGHT]).canvas
188
+ canvas.fill_color(@theme[:background]).rectangle(0, 0, WIDTH, HEIGHT).fill
189
+
190
+ ### images first, text that follows goes on top. an image covers
191
+ ### what was below (e.g. for animations), so paint the background first.
192
+ @screen.images.each do |i|
193
+ x = @left + (i[:x] - 1) * @cw
194
+ w = i[:width] * @cw
195
+ obj = i[:file]
196
+ h = w * (obj.is_a?(HexaPDF::Type::Form) ? obj.box.height / obj.box.width : obj.height.to_f / obj.width)
197
+ y = top(i[:y]) - h
198
+ canvas.fill_color(@theme[:background]).rectangle(x, y, w, h).fill
199
+ canvas.image(obj, :at => [x, y], :width => w)
200
+ end
201
+
202
+ @screen.each_run do |x,y,text,bold,color|
203
+ canvas.font(bold ? @bold : @font, :size => @fsize)
204
+ canvas.fill_color(@theme[color || (bold ? :bold : :text)])
205
+ canvas.text(text, :at => [@left + (x - 1) * @cw, baseline(y)])
206
+ end
207
+
208
+ ### footer
209
+ canvas.stroke_color(@theme[:footer]).line_width(0.5)
210
+ canvas.line(@left, top(@size - 1) - @rowh / 2, @left + @screen.columns * @cw, top(@size - 1) - @rowh / 2).stroke
211
+ canvas.font(@font, :size => @fsize).fill_color(@theme[:footer])
212
+ canvas.text(@what, :at => [@left, baseline(@size)])
213
+ num = "#{@page}/#{@scounter}"
214
+ canvas.text(num, :at => [@left + (@screen.columns - num.length) * @cw, baseline(@size)])
215
+ end #}}}
216
+
217
+ def top(y)
218
+ HEIGHT - MARGIN - (y - 1) * @rowh
219
+ end
220
+ def baseline(y)
221
+ top(y) - (@rowh + @fsize * 0.6) / 2
222
+ end
223
+ private :render, :top, :baseline
224
+
225
+ end
226
+ end
227
+ end
data/lib/output/screen.rb CHANGED
@@ -70,7 +70,7 @@ module Window #{{{
70
70
  end
71
71
  m = res.match /(?<column>\d+);(?<row>\d+)/
72
72
  [ Integer(m[:row]), Integer(m[:column]) ]
73
- rescue WinchErrror
73
+ rescue WinchError
74
74
  [0,0]
75
75
  end
76
76
  end #}}}
@@ -83,11 +83,13 @@ module Chimp
83
83
  #### mO + patternname for opening tags ####
84
84
  #### mC + patternname for closing tags ####
85
85
 
86
- def initialize() #{{{
86
+ def initialize(fname) #{{{
87
87
  @scounter = @ccounter = 0
88
88
  @what = ''
89
+ @size = SIZE
89
90
  @last = 0
90
91
  @skip = -1
92
+ @templates = []
91
93
 
92
94
  @format = Pastel.new
93
95
  @screen = Window
@@ -95,6 +97,11 @@ module Chimp
95
97
  @screen.init
96
98
  end #}}}
97
99
 
100
+ def template(tree)
101
+ @templates.each { |from,to| replay tree, from, to }
102
+ @screen.set_pos 0, 0
103
+ end
104
+
98
105
  def finish_output #{{{
99
106
  @screen.set_pos 0, @screen.lines
100
107
  @screen.print ' ' * (@screen.columns - 5)
@@ -104,6 +111,9 @@ module Chimp
104
111
  def mPP_WHAT(data)
105
112
  @what = data
106
113
  end
114
+ def mPP_SIZE(data)
115
+ @size = data if data > 0
116
+ end
107
117
 
108
118
  def mPP_SLIDES(c,tree)
109
119
  #{{{
@@ -117,21 +127,26 @@ module Chimp
117
127
  @screen.clear
118
128
  lines = @screen.lines
119
129
  columns = @screen.columns
120
- ### print mark
121
- @screen.set_pos columns, 30
122
- @screen.print "\e[0;37m^\e[0m"
123
- ### print footer
124
130
  @screen.set_pos 0, lines-1
125
- @screen.print "-"*columns
131
+ ### print footer, with a mark at the end
132
+ @screen.print (lines > @size ? ("-"*(columns-1) + "^") : "-"*columns)
126
133
  @screen.set_pos 0, lines
127
134
  @screen.print @what
128
135
  num = "#{c.userdata}/#{@scounter}"
129
- @screen.set_pos columns-num.length, lines
136
+ @screen.set_pos columns-num.length+1, lines
130
137
  @screen.print num
131
138
  @screen.set_pos 0, 0
139
+ template tree
132
140
  #}}}
133
141
  end
134
142
 
143
+ def mPP_TEMPLATE(c,tree,i)
144
+ @templates << [i+1, c.close-1]
145
+ end
146
+ def mOP_TEMPLATE(data)
147
+ raise TagSkipEvent
148
+ end
149
+
135
150
  def mPP_INCREMENTAL(c,tree,i)
136
151
  @last = c.close
137
152
  end
@@ -142,6 +157,9 @@ module Chimp
142
157
  def mCP_INCREMENTAL(c,tree,i)
143
158
  return if @skip > i
144
159
  @skip = -1
160
+ ### on the first part of the first slide there is nothing to go back to
161
+ pos = c.open-1
162
+ first = tree[pos].ttype == "P_SLIDES" && tree[0...pos].none? { |t| t.class == OpenTag && t.ttype == "P_SLIDES" }
145
163
  begin
146
164
  if @last == i
147
165
  lines = @screen.lines
@@ -154,18 +172,13 @@ module Chimp
154
172
  rescue Window::WinchError
155
173
  ch = :refresh
156
174
  end
157
- end while @last == i && ![:previous, :exit, :refresh].include?(ch)
175
+ end while (first && ch == :previous) || (@last == i && ![:previous, :exit, :refresh].include?(ch))
158
176
  case ch
159
177
  when :previous
160
178
  #{{{
161
- pos = c.open-1
162
179
  tag = tree[pos]
163
180
  if tag.ttype == "P_SLIDES"
164
- if pos == 0
165
- raise TagMoveEvent, pos
166
- else
167
- raise TagMoveEvent, tree[pos-1].open
168
- end
181
+ raise TagMoveEvent, tree[pos-1].open
169
182
  end
170
183
  if tag.ttype == "P_INCREMENTAL"
171
184
  pos = tag.open
@@ -176,7 +189,7 @@ module Chimp
176
189
  #### how many spaces needed
177
190
  howmuch = ((y - tag.userdata[:y] + 1) * columns) - (columns - x) - tag.userdata[:x]
178
191
  @screen.set_pos tag.userdata[:x], tag.userdata[:y]
179
- @screen.print x.to_s + ',' + y.to_s
192
+ # @screen.print x.to_s + ',' + y.to_s
180
193
  @screen.print ' ' * howmuch
181
194
  @screen.set_pos tag.userdata[:x], tag.userdata[:y]
182
195
  ####
@@ -197,6 +210,15 @@ module Chimp
197
210
  exit
198
211
  end
199
212
  end
213
+ def mPP_INCLUDE(data)
214
+ require File::dirname(__FILE__) + "/../plugins/#{data[:name]}.rb"
215
+ eval('Chimp::Plugin::' + data[:name].upcase).new.prepare(data[:what],data[:parameters],data[:additional])
216
+ end
217
+ def mPP_RANGE(data)
218
+ require File::dirname(__FILE__) + "/../plugins/#{data[:name]}.rb"
219
+ eval('Chimp::Plugin::' + data[:name].upcase).new.prepare(data[:what],data[:parameters],data[:additional])
220
+ end
221
+
200
222
  def mOP_INCLUDE(data)
201
223
  require File::dirname(__FILE__) + "/../plugins/#{data[:name]}.rb"
202
224
  eval('Chimp::Plugin::' + data[:name].upcase).new.process(data[:what],data[:parameters],@screen,data[:additional])
@@ -217,7 +239,9 @@ module Chimp
217
239
  def mOP_STRONGBLUE(data); @screen.print "\e[1;34m"; end
218
240
  def mCP_STRONGBLUE(data); @screen.print "\e[0m"; end
219
241
 
220
- def mOP_CENTER(data)
242
+ def mOP_CENTER(c,tree,i)
243
+ ### only the visible text counts, not the markup
244
+ data = tree[i+1...c.close].grep(String).sum(&:length)
221
245
  len = @screen.columns
222
246
  if len-data > 0
223
247
  @screen.print ' ' * ((len - data) / 2)
@@ -2,6 +2,7 @@ module Chimp
2
2
  module Plugin
3
3
  class FIGLET < Base
4
4
  def process(what,parameters,screen,additional)
5
+ screen.print "\e[1m" if parameters.include? 'bold'
5
6
  if parameters.include? 'right'
6
7
  pos = screen.get_pos
7
8
  len = screen.columns
@@ -17,6 +18,7 @@ module Chimp
17
18
  else
18
19
  screen.print `figlet #{what}`
19
20
  end
21
+ screen.print "\e[0m" if parameters.include? 'bold'
20
22
  end
21
23
  end
22
24
  end
data/lib/plugins/sixel.rb CHANGED
@@ -1,31 +1,62 @@
1
+ require 'fileutils'
2
+
1
3
  module Chimp
2
4
  module Plugin
3
5
  class SIXEL < Base
6
+ # converted images are cached in .PRESENTATIONNAME/ next to the presentation
7
+ def cache_file(what,width,fname)
8
+ dir = File.join(File.dirname(fname),'.' + File.basename(fname,'.*'))
9
+ File.join(dir,"#{what.gsub('/','_')}.#{width}.sixel")
10
+ end
11
+
12
+ def sixel(what,width,fname)
13
+ src = File.join(File.dirname(fname),what)
14
+ cache = cache_file(what,width,fname)
15
+ if File.exist?(cache) && File.exist?(src) && File.mtime(cache) >= File.mtime(src)
16
+ return File.read(cache)
17
+ end
18
+ convert = if what =~ /\.svg$/
19
+ "inkscape -D -d 300 --export-type=png #{src} -o - | img2sixel -w #{width*10}"
20
+ else
21
+ "img2sixel -w #{width*10} #{src}"
22
+ end
23
+ result = `#{convert}`
24
+ if $?.success? && !result.empty?
25
+ FileUtils.mkdir_p(File.dirname(cache))
26
+ File.write(cache,result)
27
+ end
28
+ result
29
+ end
30
+
31
+ def prepare(what,parameters,additional)
32
+ return if parameters[0] !~ /\d/
33
+ sixel(what,parameters[0].to_i,additional[:fname])
34
+ end
35
+
4
36
  def process(what,parameters,screen,additional)
5
37
  if parameters[0] !~ /\d/
6
38
  screen.print 'Use ```sixel,NUM with NUM beeing the width if the picture in characters.'
7
39
  return
8
40
  end
9
41
 
10
- printit = if what =~ /\.svg$/
11
- "inkscape -D -d 300 --export-type=png #{File.join(File.dirname(additional[:fname]),what)} -o - | img2sixel -w #{parameters[0].to_i*10}"
12
- else
13
- "img2sixel -w #{parameters[0].to_i*10} #{File.join(File.dirname(additional[:fname]),what)}"
14
- end
42
+ restore = parameters.include?('animate') || parameters.include?('float')
43
+ pos = screen.get_pos if restore
44
+ len = screen.columns
15
45
  if parameters.include? 'right'
16
- pos = screen.get_pos
17
- len = screen.columns
18
46
  screen.print " " * (len-parameters[0].to_i)
19
- screen.print `#{printit}`
20
- screen.set_pos *pos
21
47
  elsif parameters.include? 'center'
22
- len = screen.columns
23
48
  screen.print " " * ((len-parameters[0].to_i)/2)
24
- screen.print `#{printit}`
49
+ end
50
+ if screen.respond_to?(:image) # pdf output
51
+ screen.image what, parameters[0].to_i, additional[:fname]
52
+ else
53
+ screen.print sixel(what,parameters[0].to_i,additional[:fname])
54
+ end
55
+ if restore
56
+ screen.set_pos *pos
57
+ else
25
58
  x, y = screen.get_pos
26
59
  screen.set_pos 0,y
27
- else
28
- screen.print `#{printit}`
29
60
  end
30
61
  end
31
62
  end
data/tools/chimp CHANGED
@@ -8,22 +8,26 @@ require 'optparse'
8
8
  require_relative '../lib/ChimpParser'
9
9
  require_relative '../lib/ChimpPlugin'
10
10
  require_relative '../lib/ChimpParser-Grammar'
11
- require_relative '../lib/output/screen'
12
11
 
12
+ output = 'screen'
13
+ outputs = Dir[File.join(__dir__,'..','lib','output','*.rb')].map { |f| File.basename(f,'.rb') }.sort
13
14
  ARGV.options { |opt|
14
15
  opt.summary_indent = ' ' * 2
15
16
  opt.banner = "Usage:\n#{opt.summary_indent}#{File.basename($0)} [options] [FILENAME]\n"
16
17
  opt.on("Options:")
17
18
  opt.on("--help", "-h", "This text") { puts opt; exit }
19
+ opt.on("--output=NAME", "-o NAME", outputs, "Output: #{outputs.join(', ')} (default: screen)") { |o| output = o }
18
20
  opt.on("Filename needs to be a chimp presentation.")
19
21
  opt.parse!
20
- }
22
+ } or exit 1 # nil on invalid options
21
23
  if ARGV.length == 0 || !File.exist?(ARGV[0])
22
24
  puts ARGV.options
23
25
  exit
24
26
  end
25
27
 
26
- grammy = Chimp::Parser::SimpleGrammar.parse File::read(ARGV[0]), :fname => File.realpath(ARGV[0])
27
- screen = Chimp::Parser::Screen.new
28
+ fname = File.realpath(ARGV[0])
29
+ grammy = Chimp::Parser::SimpleGrammar.parse File::read(fname), :fname => fname
30
+ require_relative "../lib/output/#{output}"
31
+ screen = Chimp::Parser.const_get(output.capitalize).new fname
28
32
  grammy.prepare screen
29
33
  grammy.output screen
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cheap-impression-presenter
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juergen eTM Mangler
8
8
  bindir: tools
9
9
  cert_chain: []
10
- date: 2025-06-15 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: tty-screen
@@ -37,14 +37,28 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0.8'
40
+ - !ruby/object:Gem::Dependency
41
+ name: hexapdf
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
40
54
  description: see https://github.com/etm/chimp
41
55
  email: juergen.mangler@gmail.com
42
56
  executables:
43
57
  - chimp
44
58
  extensions: []
45
59
  extra_rdoc_files:
46
- - README.md
47
60
  - INSTALL.md
61
+ - README.md
48
62
  files:
49
63
  - AUTHORS
50
64
  - INSTALL.md
@@ -55,6 +69,7 @@ files:
55
69
  - lib/ChimpParser-Grammar.rb
56
70
  - lib/ChimpParser.rb
57
71
  - lib/ChimpPlugin.rb
72
+ - lib/output/pdf.rb
58
73
  - lib/output/screen.rb
59
74
  - lib/plugins/figlet.rb
60
75
  - lib/plugins/lnums.rb
@@ -78,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
93
  - !ruby/object:Gem::Version
79
94
  version: '0'
80
95
  requirements: []
81
- rubygems_version: 3.6.2
96
+ rubygems_version: 3.6.9
82
97
  specification_version: 4
83
98
  summary: CHIMP - Make a cheap impression. Present via terminal.
84
99
  test_files: []