Module: Przn
- Defined in:
- lib/przn.rb,
lib/przn/slide.rb,
lib/przn/theme.rb,
lib/przn/parser.rb,
lib/przn/version.rb,
lib/przn/renderer.rb,
lib/przn/terminal.rb,
lib/przn/controller.rb,
lib/przn/image_util.rb,
lib/przn/kitty_text.rb,
lib/przn/presentation.rb,
lib/przn/audience_link.rb,
lib/przn/echoes_client.rb,
lib/przn/prawn_pdf_exporter.rb,
lib/przn/presenter_renderer.rb,
lib/przn/screenshot_pdf_exporter.rb
Defined Under Namespace
Modules: AudienceLink, EchoesClient, ImageUtil, KittyText, Parser Classes: Controller, Error, PrawnPdfExporter, Presentation, PresenterRenderer, Renderer, ScreenshotPdfExporter, Slide, Terminal, Theme
Constant Summary collapse
- VERSION =
'0.4.0'
Class Method Summary collapse
-
.audience(file, socket:, theme: nil) ⇒ Object
Audience-side entry: opens the file, listens on ‘socket`, and renders whatever slide the presenter sends a `goto` for.
-
.export_pdf(file, output, theme: nil) ⇒ Object
Default PDF export: drives the live renderer, asks the terminal to save each rendered slide as a one-page vector PDF via OSC 7772 ‘capture`, then concatenates the per-slide PDFs into a single multi-page PDF.
-
.export_pdf_prawn(file, output, theme: nil) ⇒ Object
Legacy PDF export via Prawn — renders the deck directly into a vector PDF without touching the terminal.
-
.present(file, theme: nil, theme_path: nil) ⇒ Object
Presenter-side entry: detects a second display via Echoes, spawns the audience window on it, connects to the spawned process over a Unix socket, and returns a Controller wired up to drive both sides.
- .start(file, theme: nil, start_at: nil) ⇒ Object
Class Method Details
.audience(file, socket:, theme: nil) ⇒ Object
Audience-side entry: opens the file, listens on ‘socket`, and renders whatever slide the presenter sends a `goto` for. Notes are stripped. Spawned by Echoes when the presenter requests an extended-display window.
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/przn.rb', line 37 def self.audience(file, socket:, theme: nil) markdown = File.read(file) presentation = Parser.parse(markdown) terminal = Terminal.new base_dir = File.dirname(File.(file)) renderer = Renderer.new(terminal, base_dir: base_dir, theme: theme, mode: :audience) terminal.enter_alt_screen terminal.hide_cursor begin render = ->(idx, started_at) { presentation.go_to(idx) renderer.render(presentation., current: presentation.current, total: presentation.total, started_at: started_at) } render.call(0, nil) AudienceLink.serve(socket) do |msg| next unless msg[:type] == "goto" && msg[:index].is_a?(Integer) started_at = msg[:started_at] ? Time.at(msg[:started_at]) : nil render.call(msg[:index], started_at) end ensure terminal.write "\e]7772;bg-clear\a" terminal.show_cursor terminal.leave_alt_screen end end |
.export_pdf(file, output, theme: nil) ⇒ Object
Default PDF export: drives the live renderer, asks the terminal to save each rendered slide as a one-page vector PDF via OSC 7772 ‘capture`, then concatenates the per-slide PDFs into a single multi-page PDF. Requires Echoes (or any terminal that implements the same capture command); use `export_pdf_prawn` instead for environments where that’s not possible (CI, headless).
13 14 15 16 17 18 19 |
# File 'lib/przn/screenshot_pdf_exporter.rb', line 13 def self.export_pdf(file, output, theme: nil) markdown = File.read(file) presentation = Parser.parse(markdown) base_dir = File.dirname(File.(file)) ScreenshotPdfExporter.new(presentation, base_dir: base_dir, theme: theme).export(output) puts "Generated: #{output}" end |
.export_pdf_prawn(file, output, theme: nil) ⇒ Object
Legacy PDF export via Prawn — renders the deck directly into a vector PDF without touching the terminal. Diverges from what’s on screen for any feature the live renderer adds (OSC 66 sized text, OSC 7772 backgrounds, proportional fonts) but works headlessly.
39 40 41 42 43 44 45 |
# File 'lib/przn/prawn_pdf_exporter.rb', line 39 def self.export_pdf_prawn(file, output, theme: nil) markdown = File.read(file) presentation = Parser.parse(markdown) base_dir = File.dirname(File.(file)) PrawnPdfExporter.new(presentation, base_dir: base_dir, theme: theme).export(output) puts "Generated: #{output}" end |
.present(file, theme: nil, theme_path: nil) ⇒ Object
Presenter-side entry: detects a second display via Echoes, spawns the audience window on it, connects to the spawned process over a Unix socket, and returns a Controller wired up to drive both sides. Falls back to today’s mirror-mode (‘start`) when only one display is attached or Echoes is not the host terminal.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/przn.rb', line 72 def self.present(file, theme: nil, theme_path: nil) info = EchoesClient.display_info if info.nil? || info.size < 2 warn "przn: extended-display unavailable (no secondary display detected), falling back to mirror mode" return start(file, theme: theme) end socket_path = File.join(Dir.tmpdir, "przn-#{Process.pid}-#{SecureRandom.hex(4)}.sock") audience_argv = [File.($PROGRAM_NAME), '--audience', '--socket', socket_path] audience_argv += ['--theme', theme_path] if theme_path audience_argv << File.(file) EchoesClient.open_window(display: info.last[:index], argv: audience_argv) deadline = Time.now + 5 sleep 0.1 until File.exist?(socket_path) || Time.now > deadline unless File.exist?(socket_path) warn "przn: audience window did not come up within 5s, falling back to mirror mode" return start(file, theme: theme) end link = AudienceLink.connect(socket_path) link.gets # discard the {"type":"ready"} handshake markdown = File.read(file) presentation = Parser.parse(markdown) terminal = Terminal.new base_dir = File.dirname(File.(file)) renderer = PresenterRenderer.new(terminal, presentation: presentation, base_dir: base_dir, theme: theme) Controller.new(presentation, terminal, renderer, audience_link: link) end |
.start(file, theme: nil, start_at: nil) ⇒ Object
24 25 26 27 28 29 30 31 32 |
# File 'lib/przn.rb', line 24 def self.start(file, theme: nil, start_at: nil) markdown = File.read(file) presentation = Parser.parse(markdown) presentation.go_to(start_at - 1) if start_at terminal = Terminal.new base_dir = File.dirname(File.(file)) renderer = Renderer.new(terminal, base_dir: base_dir, theme: theme) Controller.new(presentation, terminal, renderer) end |