cheap-impression-presenter 1.0.1 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76030894a0e7c54a9977b6fc6f65e11d4c516191192ca10eda508ad46cff45e6
4
- data.tar.gz: 1014aa89eb276c6223f0f1529c8578e3b454d4b2d73ae9929786d2910b3e0a6a
3
+ metadata.gz: 8154b5025a9eb30f03a8b34d198e3e9024a0540ae450fb1ed4af0194982fbebf
4
+ data.tar.gz: e732455c9d6666b9d1bc672aa620f2a029cdc3d34d425478185ddfe2160222aa
5
5
  SHA512:
6
- metadata.gz: 0b788a032d8441f975d781d8cd2a9787fb2ffe12727c712af5fe837cc8f9ee37244425a75494a52b7bbc31f85b4c777c3cb24510fb644f270772c7fee9519da3
7
- data.tar.gz: de89b182400f26184b2dfe5a3c74b3d624a34b0055faf67b08aa10a5f0c3de0c51fcd035872563072ed51e47780f7b81eef088255ee2d1be7cd26dc48e02cc92
6
+ metadata.gz: d25d85a0c3c3f63f0e46af0c2234868882a1f568efd6a835595f7c682b3e279deb85ec6c2261586e9bad967daac4409ada813d1a7c6db8ff67fd323f3af376cb
7
+ data.tar.gz: cc337df9d9f494027e1e0179890c606bd4d0133ec60e0e7b5de948e0a77072c56503c690421782345fd2060ecb809a14ff6c274c3219e689de9db1855469b6aa
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,50 @@ 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 31, 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.
37
59
  * Line Numbers: display line numbers beforce any text (mostly used for code :-).
38
60
  ~~~text
39
61
  ```lnums
@@ -43,6 +65,10 @@ Line
43
65
  ```
44
66
  ~~~
45
67
 
68
+ Templates should be positioned carefully (e.g. right aligned figlet or sixel) to
69
+ not collide with the content. All header lines (#what, #size, #theme, #template)
70
+ have to be at the very beginning of the presentation.
71
+
46
72
  Run the presentation with:
47
73
 
48
74
  ```bash
@@ -51,16 +77,22 @@ chimp mypresentation.txt
51
77
 
52
78
  While presenting you can use the usual button for navigating. In addition, you
53
79
  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
+ the terminal until about 30 lines are visible. To make this easy a ``^`` character is
81
+ visible at the right end of the footer line. Zoom until the ``^`` character is no longer visible for
82
+ the most audience friendly results. The ``^`` is shown as long as more than 31 lines are visible,
83
+ use ``#size:`` directly after ``#what:`` to change this number.
57
84
 
58
85
  Check out the examples.
59
86
 
87
+ How does it look?
88
+ =================
89
+
90
+ ![CHIMP Screenshot](chimp_screenshot.png)
91
+
60
92
  History & Thanks
61
93
  ================
62
94
 
63
- Amy Hoy allowed me to use the monkey ascii art she created for the CHIMP all the way back in 2009. The
95
+ Amy Hoy all the way back in 2009 allowed me to use her monkey ascii art for the CHIMP. The
64
96
  CHIMP was written as an icebreaker for the curucamp, which was a ruby
65
97
  un-conference in 2009 in Vienna. We had fun, and many cool people from the
66
98
  community attended.
@@ -74,11 +106,28 @@ into graphics anyway, which I create with dedicated tools.
74
106
  Drop me a note if you want to be a contributor, or send me merge request if you
75
107
  have created some plugins. Creating plugins is very simple :-)
76
108
 
77
- Missing Features
78
- ================
109
+ PDF Output
110
+ ==========
111
+
112
+ To send the presentation to people in one file, write it to a PDF:
113
+
114
+ ```bash
115
+ chimp -o pdf mypresentation.txt
116
+ ```
117
+
118
+ This writes mypresentation.pdf to the directory you are currently in.
119
+
120
+ By default the slides are (default) 31 lines high, as if you zoomed the
121
+ terminal. It respects the size header. Pictures are embedded as vector
122
+ graphics for SVGs or as images for everything else.
123
+
124
+ The default colors are solarized light. Change them with a ``#theme:`` line in
125
+ the header, holding KEY=#RRGGBB pairs for background, text, bold, red, blue and
126
+ footer. Colors you leave out keep their default. Solarized dark for example:
127
+
128
+ #theme: background=#002b36 text=#839496 bold=#93a1a1 red=#dc322f blue=#268bd2 footer=#586e75
79
129
 
80
- Presenting is nice. A PDF output mode would be useful ... to send the presentation
81
- to people in one file.
130
+ examples/chimp.txt (light) and examples/markus.txt (dark) show both.
82
131
 
83
132
  LICENSE
84
133
  =======
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.0"
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
34
  GP_INCREMENTAL = [ 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
@@ -38,6 +38,35 @@ module Chimp
38
38
  def string(data)
39
39
  data
40
40
  end
41
+
42
+ #### calls the output method for element i of the tree, raises NameError if there is none
43
+ def dispatch(tree,i)
44
+ c = tree[i]
45
+ met, data = case c
46
+ when String then [method("string"), c]
47
+ when OpenTag then [method("mO" + c.ttype), c.data]
48
+ when CloseTag then [method("mC" + c.ttype), tree[c.open].data]
49
+ end
50
+ case met.arity
51
+ when 0; met.call
52
+ when 1; met.call(data)
53
+ when 2; met.call(c,tree)
54
+ when 3; met.call(c,tree,i)
55
+ when 4; met.call(c,tree,i,data)
56
+ else
57
+ ""
58
+ end
59
+ end
60
+
61
+ #### outputs a part of the tree again (e.g. a template for every slide)
62
+ def replay(tree,from,to)
63
+ from.upto(to) do |i|
64
+ begin
65
+ dispatch(tree,i)
66
+ rescue NameError
67
+ end
68
+ end
69
+ end
41
70
  end
42
71
 
43
72
  class Grammar
@@ -83,26 +112,7 @@ module Chimp
83
112
  while @tree.length > i
84
113
  c = @tree[i]
85
114
  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
115
+ out << output.dispatch(@tree,i).to_s
106
116
 
107
117
  i += 1
108
118
  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,226 @@
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 = 31
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
+ def mPP_THEME(data) #{{{
143
+ data.split.each do |pair|
144
+ key, color = pair.split('=',2)
145
+ unless THEME.key?(key.to_sym) && color =~ /\A#?(\h{6})\z/
146
+ abort "Invalid #theme: entry '#{pair}', use KEY=#RRGGBB with KEY one of: #{THEME.keys.join(', ')}"
147
+ end
148
+ @theme[key.to_sym] = $1
149
+ end
150
+ end #}}}
151
+
152
+ def mOP_SLIDES(c,tree)
153
+ @page = c.userdata
154
+ @screen.clear
155
+ template tree
156
+ end
157
+ #### every +++ step (animation frame) becomes a page of its own
158
+ def mOP_INCREMENTAL(c,tree); end
159
+ def mCP_INCREMENTAL(c,tree,i)
160
+ render
161
+ end
162
+
163
+ #### plugins only need to prepare things for the terminal
164
+ def mPP_INCLUDE(data); end
165
+ def mPP_RANGE(data); end
166
+
167
+ #### an image in the pdf, svgs are converted to pdf with inkscape (cached)
168
+ def load_image(what,width,fname) #{{{
169
+ src = File.join(File.dirname(fname),what)
170
+ return unless File.exist?(src)
171
+ if what =~ /\.svg$/
172
+ cache = File.join(File.dirname(fname),'.' + File.basename(fname,'.*'),"#{what.gsub('/','_')}.pdf")
173
+ unless File.exist?(cache) && File.mtime(cache) >= File.mtime(src)
174
+ FileUtils.mkdir_p(File.dirname(cache))
175
+ system 'inkscape', '-D', '--export-type=pdf', src, '-o', cache, :err => File::NULL
176
+ end
177
+ src = cache
178
+ end
179
+ img = @doc.images.add(src)
180
+ w, h = img.is_a?(HexaPDF::Type::Form) ? [img.box.width, img.box.height] : [img.width, img.height]
181
+ [img, (width * @cw * h / w / @rowh).ceil]
182
+ end #}}}
183
+
184
+ def render #{{{
185
+ return if @page.nil?
186
+ canvas = @doc.pages.add([0, 0, WIDTH, HEIGHT]).canvas
187
+ canvas.fill_color(@theme[:background]).rectangle(0, 0, WIDTH, HEIGHT).fill
188
+
189
+ ### images first, text that follows goes on top. an image covers
190
+ ### what was below (e.g. for animations), so paint the background first.
191
+ @screen.images.each do |i|
192
+ x = @left + (i[:x] - 1) * @cw
193
+ w = i[:width] * @cw
194
+ obj = i[:file]
195
+ h = w * (obj.is_a?(HexaPDF::Type::Form) ? obj.box.height / obj.box.width : obj.height.to_f / obj.width)
196
+ y = top(i[:y]) - h
197
+ canvas.fill_color(@theme[:background]).rectangle(x, y, w, h).fill
198
+ canvas.image(obj, :at => [x, y], :width => w)
199
+ end
200
+
201
+ @screen.each_run do |x,y,text,bold,color|
202
+ canvas.font(bold ? @bold : @font, :size => @fsize)
203
+ canvas.fill_color(@theme[color || (bold ? :bold : :text)])
204
+ canvas.text(text, :at => [@left + (x - 1) * @cw, baseline(y)])
205
+ end
206
+
207
+ ### footer
208
+ canvas.stroke_color(@theme[:footer]).line_width(0.5)
209
+ canvas.line(@left, top(@size - 1) - @rowh / 2, @left + @screen.columns * @cw, top(@size - 1) - @rowh / 2).stroke
210
+ canvas.font(@font, :size => @fsize).fill_color(@theme[:footer])
211
+ canvas.text(@what, :at => [@left, baseline(@size)])
212
+ num = "#{@page}/#{@scounter}"
213
+ canvas.text(num, :at => [@left + (@screen.columns - num.length) * @cw, baseline(@size)])
214
+ end #}}}
215
+
216
+ def top(y)
217
+ HEIGHT - MARGIN - (y - 1) * @rowh
218
+ end
219
+ def baseline(y)
220
+ top(y) - (@rowh + @fsize * 0.6) / 2
221
+ end
222
+ private :render, :top, :baseline
223
+
224
+ end
225
+ end
226
+ 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 = 31
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)
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.0
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: []