prezo 2026.1.1__tar.gz → 2026.1.3__tar.gz
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.
- {prezo-2026.1.1 → prezo-2026.1.3}/PKG-INFO +25 -4
- {prezo-2026.1.1 → prezo-2026.1.3}/README.md +24 -3
- {prezo-2026.1.1 → prezo-2026.1.3}/pyproject.toml +11 -3
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/__init__.py +8 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/app.py +25 -31
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/config.py +1 -1
- prezo-2026.1.3/src/prezo/export/__init__.py +36 -0
- prezo-2026.1.3/src/prezo/export/common.py +77 -0
- prezo-2026.1.3/src/prezo/export/html.py +340 -0
- prezo-2026.1.3/src/prezo/export/images.py +261 -0
- prezo-2026.1.3/src/prezo/export/pdf.py +497 -0
- prezo-2026.1.3/src/prezo/export/svg.py +170 -0
- prezo-2026.1.3/src/prezo/layout.py +680 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/parser.py +11 -4
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/widgets/__init__.py +9 -1
- prezo-2026.1.3/src/prezo/widgets/slide_content.py +81 -0
- prezo-2026.1.1/src/prezo/export.py +0 -835
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/images/__init__.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/images/ascii.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/images/base.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/images/chafa.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/images/iterm.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/images/kitty.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/images/overlay.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/images/processor.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/images/sixel.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/screens/__init__.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/screens/base.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/screens/blackout.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/screens/goto.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/screens/help.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/screens/overview.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/screens/search.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/screens/toc.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/terminal.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/themes.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/widgets/image_display.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/widgets/slide_button.py +0 -0
- {prezo-2026.1.1 → prezo-2026.1.3}/src/prezo/widgets/status_bar.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: prezo
|
|
3
|
-
Version: 2026.1.
|
|
3
|
+
Version: 2026.1.3
|
|
4
4
|
Summary: A TUI-based presentation tool for the terminal, built with Textual.
|
|
5
5
|
Author: Stefane Fermigier
|
|
6
6
|
Author-email: Stefane Fermigier <sf@fermigier.com>
|
|
@@ -16,9 +16,10 @@ A TUI-based presentation tool for the terminal, built with [Textual](https://tex
|
|
|
16
16
|
|
|
17
17
|
Display presentations written in Markdown using conventions similar to those of [MARP](https://marp.app/) or [Deckset](https://www.deckset.com/).
|
|
18
18
|
|
|
19
|
-
## Features
|
|
19
|
+
## Features
|
|
20
20
|
|
|
21
21
|
- **Markdown presentations** - MARP/Deckset format with `---` slide separators
|
|
22
|
+
- **Column layouts** - Pandoc-style fenced divs for multi-column slides (`::: columns`)
|
|
22
23
|
- **Live reload** - Auto-refresh when file changes (1s polling)
|
|
23
24
|
- **Keyboard navigation** - Vim-style keys, arrow keys, and more
|
|
24
25
|
- **Slide overview** - Grid view for quick navigation (`o`)
|
|
@@ -130,12 +131,32 @@ Presenter notes go here (after ???)
|
|
|
130
131
|
|
|
131
132
|
# Third Slide
|
|
132
133
|
|
|
134
|
+
::: columns
|
|
135
|
+
::: column
|
|
136
|
+
**Left Column**
|
|
137
|
+
- Point A
|
|
138
|
+
- Point B
|
|
139
|
+
:::
|
|
140
|
+
|
|
141
|
+
::: column
|
|
142
|
+
**Right Column**
|
|
143
|
+
- Point C
|
|
144
|
+
- Point D
|
|
145
|
+
:::
|
|
146
|
+
:::
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
# Fourth Slide
|
|
151
|
+
|
|
133
152
|
<!-- notes: Alternative notes syntax -->
|
|
134
153
|
|
|
135
|
-
|
|
154
|
+
::: center
|
|
155
|
+
**Centered content**
|
|
156
|
+
:::
|
|
136
157
|
```
|
|
137
158
|
|
|
138
|
-
See the [Writing Presentations in Markdown](docs/tutorial.md) tutorial for a complete guide on creating presentations, including images, presenter notes, and configuration directives.
|
|
159
|
+
See the [Writing Presentations in Markdown](docs/tutorial.md) tutorial for a complete guide on creating presentations, including column layouts, images, presenter notes, and configuration directives.
|
|
139
160
|
|
|
140
161
|
## Themes
|
|
141
162
|
|
|
@@ -4,9 +4,10 @@ A TUI-based presentation tool for the terminal, built with [Textual](https://tex
|
|
|
4
4
|
|
|
5
5
|
Display presentations written in Markdown using conventions similar to those of [MARP](https://marp.app/) or [Deckset](https://www.deckset.com/).
|
|
6
6
|
|
|
7
|
-
## Features
|
|
7
|
+
## Features
|
|
8
8
|
|
|
9
9
|
- **Markdown presentations** - MARP/Deckset format with `---` slide separators
|
|
10
|
+
- **Column layouts** - Pandoc-style fenced divs for multi-column slides (`::: columns`)
|
|
10
11
|
- **Live reload** - Auto-refresh when file changes (1s polling)
|
|
11
12
|
- **Keyboard navigation** - Vim-style keys, arrow keys, and more
|
|
12
13
|
- **Slide overview** - Grid view for quick navigation (`o`)
|
|
@@ -118,12 +119,32 @@ Presenter notes go here (after ???)
|
|
|
118
119
|
|
|
119
120
|
# Third Slide
|
|
120
121
|
|
|
122
|
+
::: columns
|
|
123
|
+
::: column
|
|
124
|
+
**Left Column**
|
|
125
|
+
- Point A
|
|
126
|
+
- Point B
|
|
127
|
+
:::
|
|
128
|
+
|
|
129
|
+
::: column
|
|
130
|
+
**Right Column**
|
|
131
|
+
- Point C
|
|
132
|
+
- Point D
|
|
133
|
+
:::
|
|
134
|
+
:::
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
# Fourth Slide
|
|
139
|
+
|
|
121
140
|
<!-- notes: Alternative notes syntax -->
|
|
122
141
|
|
|
123
|
-
|
|
142
|
+
::: center
|
|
143
|
+
**Centered content**
|
|
144
|
+
:::
|
|
124
145
|
```
|
|
125
146
|
|
|
126
|
-
See the [Writing Presentations in Markdown](docs/tutorial.md) tutorial for a complete guide on creating presentations, including images, presenter notes, and configuration directives.
|
|
147
|
+
See the [Writing Presentations in Markdown](docs/tutorial.md) tutorial for a complete guide on creating presentations, including column layouts, images, presenter notes, and configuration directives.
|
|
127
148
|
|
|
128
149
|
## Themes
|
|
129
150
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "prezo"
|
|
3
|
-
version = "2026.1.
|
|
3
|
+
version = "2026.1.3"
|
|
4
4
|
description = "A TUI-based presentation tool for the terminal, built with Textual."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -18,14 +18,16 @@ prezo = "prezo:main"
|
|
|
18
18
|
|
|
19
19
|
[dependency-groups]
|
|
20
20
|
dev = [
|
|
21
|
+
"abilian-devtools>=0.8.0",
|
|
22
|
+
# Testing
|
|
21
23
|
"pytest>=8.0.0",
|
|
22
24
|
"pytest-asyncio>=1.3.0",
|
|
23
|
-
|
|
25
|
+
# Typechecking
|
|
24
26
|
"ty>=0.0.2",
|
|
27
|
+
"types-markdown>=3.10.0.20251106",
|
|
25
28
|
# See below
|
|
26
29
|
"cairosvg>=2.7.0",
|
|
27
30
|
"pypdf>=4.0.0",
|
|
28
|
-
"types-markdown>=3.10.0.20251106",
|
|
29
31
|
]
|
|
30
32
|
export = [
|
|
31
33
|
"cairosvg>=2.7.0",
|
|
@@ -36,6 +38,12 @@ export = [
|
|
|
36
38
|
testpaths = ["tests"]
|
|
37
39
|
asyncio_mode = "auto"
|
|
38
40
|
asyncio_default_fixture_loop_scope = "function"
|
|
41
|
+
markers = [
|
|
42
|
+
"unit: Unit tests (fast, isolated, no I/O)",
|
|
43
|
+
"integration: Integration tests (component interactions, file I/O)",
|
|
44
|
+
"e2e: End-to-end tests (full app/CLI workflows)",
|
|
45
|
+
"slow: Slow tests that can be skipped with -m 'not slow'",
|
|
46
|
+
]
|
|
39
47
|
|
|
40
48
|
[build-system]
|
|
41
49
|
requires = ["uv_build>=0.8.4,<0.9.0"]
|
|
@@ -128,6 +128,13 @@ def main() -> None:
|
|
|
128
128
|
action="store_true",
|
|
129
129
|
help="Export without window decorations (for printing)",
|
|
130
130
|
)
|
|
131
|
+
parser.add_argument(
|
|
132
|
+
"--pdf-backend",
|
|
133
|
+
metavar="BACKEND",
|
|
134
|
+
choices=["auto", "chrome", "inkscape", "cairosvg"],
|
|
135
|
+
default="auto",
|
|
136
|
+
help="PDF conversion backend (auto, chrome, inkscape, cairosvg). Default: auto",
|
|
137
|
+
)
|
|
131
138
|
parser.add_argument(
|
|
132
139
|
"--scale",
|
|
133
140
|
metavar="FACTOR",
|
|
@@ -218,6 +225,7 @@ def main() -> None:
|
|
|
218
225
|
width=width,
|
|
219
226
|
height=height,
|
|
220
227
|
chrome=not args.no_chrome,
|
|
228
|
+
pdf_backend=args.pdf_backend,
|
|
221
229
|
),
|
|
222
230
|
)
|
|
223
231
|
else:
|
|
@@ -39,7 +39,7 @@ from .screens import (
|
|
|
39
39
|
)
|
|
40
40
|
from .terminal import ImageCapability, detect_image_capability
|
|
41
41
|
from .themes import get_next_theme, get_theme
|
|
42
|
-
from .widgets import ImageDisplay, StatusBar
|
|
42
|
+
from .widgets import ImageDisplay, SlideContent, StatusBar
|
|
43
43
|
|
|
44
44
|
WELCOME_MESSAGE = """\
|
|
45
45
|
# Welcome to Prezo
|
|
@@ -199,7 +199,7 @@ class PrezoApp(App):
|
|
|
199
199
|
|
|
200
200
|
ENABLE_COMMAND_PALETTE = True
|
|
201
201
|
COMMAND_PALETTE_BINDING = "ctrl+p"
|
|
202
|
-
COMMANDS: ClassVar[set[type[Provider]]] = {PrezoCommands}
|
|
202
|
+
COMMANDS: ClassVar[set[type[Provider]]] = {PrezoCommands}
|
|
203
203
|
|
|
204
204
|
CSS = """
|
|
205
205
|
Screen {
|
|
@@ -389,7 +389,7 @@ class PrezoApp(App):
|
|
|
389
389
|
yield ImageDisplay(id="slide-image")
|
|
390
390
|
# Text container
|
|
391
391
|
with VerticalScroll(id="slide-container"):
|
|
392
|
-
yield
|
|
392
|
+
yield SlideContent("", id="slide-content")
|
|
393
393
|
with Vertical(id="notes-panel"):
|
|
394
394
|
yield Static("Notes", id="notes-title")
|
|
395
395
|
yield Markdown("", id="notes-content")
|
|
@@ -519,7 +519,7 @@ class PrezoApp(App):
|
|
|
519
519
|
recent_section = _format_recent_files(self.state.recent_files)
|
|
520
520
|
if recent_section:
|
|
521
521
|
welcome += recent_section
|
|
522
|
-
self.query_one("#slide-content",
|
|
522
|
+
self.query_one("#slide-content", SlideContent).set_content(welcome)
|
|
523
523
|
status = self.query_one("#status-bar", StatusBar)
|
|
524
524
|
status.current = 0
|
|
525
525
|
status.total = 1
|
|
@@ -555,35 +555,27 @@ class PrezoApp(App):
|
|
|
555
555
|
image_container.add_class("visible")
|
|
556
556
|
|
|
557
557
|
# Apply layout based on MARP directive
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
image_container, before=slide_container
|
|
575
|
-
)
|
|
576
|
-
elif layout in ("background", "fit"):
|
|
577
|
-
# Background/fit images: show image full width behind/above text
|
|
578
|
-
image_container.add_class("layout-inline")
|
|
579
|
-
horizontal_container.move_child(
|
|
580
|
-
image_container, before=slide_container
|
|
581
|
-
)
|
|
558
|
+
match first_image.layout:
|
|
559
|
+
case "left":
|
|
560
|
+
image_container.add_class("layout-left")
|
|
561
|
+
horizontal_container.move_child(
|
|
562
|
+
image_container, before=slide_container
|
|
563
|
+
)
|
|
564
|
+
case "right":
|
|
565
|
+
image_container.add_class("layout-right")
|
|
566
|
+
horizontal_container.move_child(
|
|
567
|
+
image_container, after=slide_container
|
|
568
|
+
)
|
|
569
|
+
case "inline" | "background" | "fit":
|
|
570
|
+
image_container.add_class("layout-inline")
|
|
571
|
+
horizontal_container.move_child(
|
|
572
|
+
image_container, before=slide_container
|
|
573
|
+
)
|
|
582
574
|
|
|
583
575
|
# Apply dynamic width if size_percent is specified
|
|
584
576
|
default_size = 50
|
|
585
577
|
has_custom_size = first_image.size_percent != default_size
|
|
586
|
-
if has_custom_size and layout in ("left", "right"):
|
|
578
|
+
if has_custom_size and first_image.layout in ("left", "right"):
|
|
587
579
|
image_container.styles.width = f"{first_image.size_percent}%"
|
|
588
580
|
else:
|
|
589
581
|
image_container.styles.width = None # Reset to CSS default
|
|
@@ -591,7 +583,9 @@ class PrezoApp(App):
|
|
|
591
583
|
image_widget.clear()
|
|
592
584
|
|
|
593
585
|
# Use cleaned content (bg images already removed by parser)
|
|
594
|
-
self.query_one("#slide-content",
|
|
586
|
+
self.query_one("#slide-content", SlideContent).set_content(
|
|
587
|
+
slide.content.strip()
|
|
588
|
+
)
|
|
595
589
|
|
|
596
590
|
container = self.query_one("#slide-container", VerticalScroll)
|
|
597
591
|
container.scroll_home(animate=False)
|
|
@@ -738,7 +732,7 @@ class PrezoApp(App):
|
|
|
738
732
|
def watch_app_theme(self, theme_name: str) -> None:
|
|
739
733
|
"""Apply theme when it changes."""
|
|
740
734
|
# Only apply to widgets after mount (watcher fires during init)
|
|
741
|
-
if not self.is_mounted:
|
|
735
|
+
if not self.is_mounted:
|
|
742
736
|
return
|
|
743
737
|
self._apply_theme(theme_name)
|
|
744
738
|
self.notify(f"Theme: {theme_name}", timeout=1)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Export functionality for prezo presentations.
|
|
2
|
+
|
|
3
|
+
Exports presentations to PDF, HTML, PNG, and SVG formats.
|
|
4
|
+
|
|
5
|
+
IMPORTANT: PDF/PNG/SVG export must be a faithful image of the TUI console.
|
|
6
|
+
This requires proper monospace font loading. If Fira Code is not available,
|
|
7
|
+
alignment may be incorrect.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .common import (
|
|
13
|
+
ExportError,
|
|
14
|
+
check_font_availability,
|
|
15
|
+
print_font_warnings,
|
|
16
|
+
)
|
|
17
|
+
from .html import export_to_html, render_slide_to_html, run_html_export
|
|
18
|
+
from .images import export_slide_to_image, export_to_images, run_image_export
|
|
19
|
+
from .pdf import combine_svgs_to_pdf, export_to_pdf, run_export
|
|
20
|
+
from .svg import render_slide_to_svg
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"ExportError",
|
|
24
|
+
"check_font_availability",
|
|
25
|
+
"combine_svgs_to_pdf",
|
|
26
|
+
"export_slide_to_image",
|
|
27
|
+
"export_to_html",
|
|
28
|
+
"export_to_images",
|
|
29
|
+
"export_to_pdf",
|
|
30
|
+
"print_font_warnings",
|
|
31
|
+
"render_slide_to_html",
|
|
32
|
+
"render_slide_to_svg",
|
|
33
|
+
"run_export",
|
|
34
|
+
"run_html_export",
|
|
35
|
+
"run_image_export",
|
|
36
|
+
]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Common utilities and constants for export functionality."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ExportError(Exception):
|
|
11
|
+
"""Raised when an export operation fails."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Exit codes for CLI (only used in run_* wrapper functions)
|
|
15
|
+
EXIT_SUCCESS = 0
|
|
16
|
+
EXIT_FAILURE = 2
|
|
17
|
+
|
|
18
|
+
# Backwards compatibility aliases (deprecated, use exceptions instead)
|
|
19
|
+
EXPORT_SUCCESS = EXIT_SUCCESS
|
|
20
|
+
EXPORT_FAILED = EXIT_FAILURE
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def check_font_availability() -> list[str]:
|
|
24
|
+
"""Check if required fonts are available on the system.
|
|
25
|
+
|
|
26
|
+
Returns a list of warning messages (empty if all fonts are available).
|
|
27
|
+
"""
|
|
28
|
+
warnings = []
|
|
29
|
+
|
|
30
|
+
# Check for fc-list (fontconfig) to query system fonts
|
|
31
|
+
fc_list_path = shutil.which("fc-list")
|
|
32
|
+
if fc_list_path:
|
|
33
|
+
try:
|
|
34
|
+
result = subprocess.run(
|
|
35
|
+
[fc_list_path, ":family"],
|
|
36
|
+
capture_output=True,
|
|
37
|
+
text=True,
|
|
38
|
+
timeout=5,
|
|
39
|
+
check=False,
|
|
40
|
+
)
|
|
41
|
+
fonts = result.stdout.lower()
|
|
42
|
+
|
|
43
|
+
# Check for Fira Code (primary monospace font)
|
|
44
|
+
if "fira code" not in fonts and "firacode" not in fonts:
|
|
45
|
+
warnings.append(
|
|
46
|
+
"Fira Code font not found. Install it for best results:\n"
|
|
47
|
+
" macOS: brew install --cask font-fira-code\n"
|
|
48
|
+
" Ubuntu: sudo apt install fonts-firacode\n"
|
|
49
|
+
" Or download from: https://github.com/tonsky/FiraCode"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
except (subprocess.TimeoutExpired, subprocess.SubprocessError):
|
|
53
|
+
# Can't check fonts, skip warning
|
|
54
|
+
pass
|
|
55
|
+
else:
|
|
56
|
+
# No fontconfig available (Windows or minimal system)
|
|
57
|
+
# We can't easily check fonts, so just note the requirement
|
|
58
|
+
warnings.append(
|
|
59
|
+
"Cannot verify font availability. For correct alignment, ensure "
|
|
60
|
+
"Fira Code font is installed."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
return warnings
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def print_font_warnings(warnings: list[str]) -> None:
|
|
67
|
+
"""Print font warnings to stderr."""
|
|
68
|
+
if warnings:
|
|
69
|
+
print("\n⚠️ Font Warning:", file=sys.stderr)
|
|
70
|
+
for warning in warnings:
|
|
71
|
+
for line in warning.split("\n"):
|
|
72
|
+
print(f" {line}", file=sys.stderr)
|
|
73
|
+
print(
|
|
74
|
+
"\n Without proper fonts, column alignment may be incorrect in exports.",
|
|
75
|
+
file=sys.stderr,
|
|
76
|
+
)
|
|
77
|
+
print(file=sys.stderr)
|