przn 0.3.0 → 0.5.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.
data/lib/przn.rb CHANGED
@@ -1,17 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "przn/version"
4
- require_relative "przn/kitty_text"
5
- require_relative "przn/image_util"
6
- require_relative "przn/slide"
7
- require_relative "przn/parser"
8
- require_relative "przn/presentation"
9
- require_relative "przn/terminal"
10
- require_relative "przn/renderer"
11
- require_relative "przn/controller"
12
- require_relative "przn/pdf_exporter"
13
- require_relative "przn/screenshot_pdf_exporter"
14
- require_relative "przn/theme"
3
+ require 'tmpdir'
4
+ require 'securerandom'
5
+
6
+ require_relative 'przn/version'
7
+ require_relative 'przn/kitty_text'
8
+ require_relative 'przn/image_util'
9
+ require_relative 'przn/parser'
10
+ require_relative 'przn/slide'
11
+ require_relative 'przn/presentation'
12
+ require_relative 'przn/terminal'
13
+ require_relative 'przn/renderer'
14
+ require_relative 'przn/presenter_renderer'
15
+ require_relative 'przn/audience_link'
16
+ require_relative 'przn/echoes_client'
17
+ require_relative 'przn/controller'
18
+ require_relative 'przn/screenshot_pdf_exporter'
19
+ require_relative 'przn/theme'
15
20
 
16
21
  module Przn
17
22
  class Error < StandardError; end
@@ -26,29 +31,73 @@ module Przn
26
31
  Controller.new(presentation, terminal, renderer)
27
32
  end
28
33
 
29
- # Default PDF export: drives the live renderer, asks the terminal to save
30
- # each rendered slide as a one-page vector PDF via OSC 7772 `capture`,
31
- # then concatenates the per-slide PDFs into a single multi-page PDF.
32
- # Requires Echoes (or any terminal that implements the same capture
33
- # command); use `export_pdf_prawn` instead for environments where that's
34
- # not possible (CI, headless).
35
- def self.export_pdf(file, output, theme: nil)
34
+ # Audience-side entry: opens the file, listens on `socket`, and renders
35
+ # whatever slide the presenter sends a `goto` for. Notes are stripped.
36
+ # Spawned by Echoes when the presenter requests an extended-display window.
37
+ def self.audience(file, socket:, theme: nil)
36
38
  markdown = File.read(file)
37
39
  presentation = Parser.parse(markdown)
40
+ terminal = Terminal.new
38
41
  base_dir = File.dirname(File.expand_path(file))
39
- ScreenshotPdfExporter.new(presentation, base_dir: base_dir, theme: theme).export(output)
40
- puts "Generated: #{output}"
42
+ renderer = Renderer.new(terminal, base_dir: base_dir, theme: theme, mode: :audience)
43
+
44
+ terminal.enter_alt_screen
45
+ terminal.hide_cursor
46
+ begin
47
+ render = ->(idx, started_at) {
48
+ presentation.go_to(idx)
49
+ renderer.render(presentation.current_slide,
50
+ current: presentation.current,
51
+ total: presentation.total,
52
+ started_at: started_at)
53
+ }
54
+ render.call(0, nil)
55
+ AudienceLink.serve(socket) do |msg|
56
+ next unless msg[:type] == "goto" && msg[:index].is_a?(Integer)
57
+ started_at = msg[:started_at] ? Time.at(msg[:started_at]) : nil
58
+ render.call(msg[:index], started_at)
59
+ end
60
+ ensure
61
+ terminal.write "\e]7772;bg-clear\a"
62
+ terminal.write ImageUtil.kitty_clear_all if ImageUtil.kitty_terminal?
63
+ terminal.show_cursor
64
+ terminal.leave_alt_screen
65
+ end
41
66
  end
42
67
 
43
- # Legacy PDF export via Prawn renders the deck directly into a vector
44
- # PDF without touching the terminal. Diverges from what's on screen for
45
- # any feature the live renderer adds (OSC 66 sized text, OSC 7772
46
- # backgrounds, proportional fonts) but works headlessly.
47
- def self.export_pdf_prawn(file, output, theme: nil)
68
+ # Presenter-side entry: detects a second display via Echoes, spawns the
69
+ # audience window on it, connects to the spawned process over a Unix
70
+ # socket, and returns a Controller wired up to drive both sides.
71
+ # Falls back to today's mirror-mode (`start`) when only one display is
72
+ # attached or Echoes is not the host terminal.
73
+ def self.present(file, theme: nil, theme_path: nil)
74
+ info = EchoesClient.display_info
75
+ if info.nil? || info.size < 2
76
+ warn "przn: extended-display unavailable (no secondary display detected), falling back to mirror mode"
77
+ return start(file, theme: theme)
78
+ end
79
+
80
+ socket_path = File.join(Dir.tmpdir, "przn-#{Process.pid}-#{SecureRandom.hex(4)}.sock")
81
+ audience_argv = [File.expand_path($PROGRAM_NAME), '--audience', '--socket', socket_path]
82
+ audience_argv += ['--theme', theme_path] if theme_path
83
+ audience_argv << File.expand_path(file)
84
+ EchoesClient.open_window(display: info.last[:index], argv: audience_argv)
85
+
86
+ deadline = Time.now + 5
87
+ sleep 0.1 until File.exist?(socket_path) || Time.now > deadline
88
+ unless File.exist?(socket_path)
89
+ warn "przn: audience window did not come up within 5s, falling back to mirror mode"
90
+ return start(file, theme: theme)
91
+ end
92
+
93
+ link = AudienceLink.connect(socket_path)
94
+ link.gets # discard the {"type":"ready"} handshake
95
+
48
96
  markdown = File.read(file)
49
97
  presentation = Parser.parse(markdown)
98
+ terminal = Terminal.new
50
99
  base_dir = File.dirname(File.expand_path(file))
51
- PdfExporter.new(presentation, base_dir: base_dir, theme: theme).export(output)
52
- puts "Generated: #{output}"
100
+ renderer = PresenterRenderer.new(terminal, presentation: presentation, base_dir: base_dir, theme: theme)
101
+ Controller.new(presentation, terminal, renderer, audience_link: link)
53
102
  end
54
103
  end
data/sample/doge.jpg ADDED
Binary file
data/sample/doge.png ADDED
Binary file
data/sample/sample.md CHANGED
@@ -40,6 +40,30 @@ This is **bold**, this is *italic*, and this is `inline code`.
40
40
 
41
41
  normal and {::tag name="red"}red text{:/tag} mixed
42
42
 
43
+ # Image (PNG)
44
+
45
+ ![](doge.png){:relative_height="70"}
46
+
47
+ # Image (JPG)
48
+
49
+ ![](doge.jpg){:relative_height="70"}
50
+
51
+ # Image (XML form)
52
+
53
+ <img src="doge.png" relative_height="70"/>
54
+
55
+ # Image (absolute position)
56
+
57
+ <img src="doge.png" x="5" y="3" relative_height="40"/>
58
+ <img src="doge.png" x="50%" y="50%" relative_height="40"/>
59
+
60
+ # Absolute-position text
61
+
62
+ <at x="10" y="10">top-left ish</at>
63
+ <at x="40" y="15"><size=3>BIG</size></at>
64
+ <at x="80" y="25"><color=red>warn</color></at>
65
+ <at x="50%" y="50%">dead center</at>
66
+
43
67
  # Thank You!
44
68
 
45
69
  That's all! Enjoy!
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: przn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Akira Matsuda
@@ -55,18 +55,23 @@ files:
55
55
  - default_theme.yml
56
56
  - exe/przn
57
57
  - lib/przn.rb
58
+ - lib/przn/audience_link.rb
58
59
  - lib/przn/controller.rb
60
+ - lib/przn/echoes_client.rb
59
61
  - lib/przn/image_util.rb
60
62
  - lib/przn/kitty_text.rb
61
63
  - lib/przn/parser.rb
62
- - lib/przn/pdf_exporter.rb
64
+ - lib/przn/prawn_pdf_exporter.rb
63
65
  - lib/przn/presentation.rb
66
+ - lib/przn/presenter_renderer.rb
64
67
  - lib/przn/renderer.rb
65
68
  - lib/przn/screenshot_pdf_exporter.rb
66
69
  - lib/przn/slide.rb
67
70
  - lib/przn/terminal.rb
68
71
  - lib/przn/theme.rb
69
72
  - lib/przn/version.rb
73
+ - sample/doge.jpg
74
+ - sample/doge.png
70
75
  - sample/sample.md
71
76
  - sig/przn.rbs
72
77
  homepage: "https://github.com/amatsuda/przn"