mirror of
https://github.com/mfontanini/presenterm.git
synced 2025-05-05 15:32:58 +00:00
feat: allow the ability to create html outputs
This commit is contained in:
parent
c3fb212f90
commit
0d4ffceede
@ -2,7 +2,7 @@ use crate::{
|
||||
MarkdownParser, Resources,
|
||||
code::execute::SnippetExecutor,
|
||||
config::{KeyBindingsConfig, PauseExportPolicy},
|
||||
export::pdf::PdfRender,
|
||||
export::pdf::{ExportRenderer, Renderer},
|
||||
markdown::{parse::ParseError, text_style::Color},
|
||||
presentation::{
|
||||
Presentation,
|
||||
@ -98,24 +98,12 @@ impl<'a> Exporter<'a> {
|
||||
Self { parser, default_theme, resources, third_party, code_executor, themes, options, dimensions }
|
||||
}
|
||||
|
||||
/// Export the given presentation into PDF.
|
||||
///
|
||||
/// This uses a separate `presenterm-export` tool.
|
||||
pub fn export_pdf(
|
||||
mut self,
|
||||
fn build_renderer(
|
||||
&mut self,
|
||||
presentation_path: &Path,
|
||||
output_directory: OutputDirectory,
|
||||
output_path: Option<&Path>,
|
||||
) -> Result<(), ExportError> {
|
||||
println!(
|
||||
"exporting using rows={}, columns={}, width={}, height={}",
|
||||
self.dimensions.rows, self.dimensions.columns, self.dimensions.width, self.dimensions.height
|
||||
);
|
||||
|
||||
println!("checking for weasyprint...");
|
||||
Self::validate_weasyprint_exists()?;
|
||||
Self::log("weasyprint installation found")?;
|
||||
|
||||
renderer: Renderer,
|
||||
) -> Result<ExportRenderer, ExportError> {
|
||||
let content = fs::read_to_string(presentation_path).map_err(ExportError::ReadPresentation)?;
|
||||
let elements = self.parser.parse(&content)?;
|
||||
|
||||
@ -132,7 +120,7 @@ impl<'a> Exporter<'a> {
|
||||
.build(elements)?;
|
||||
Self::validate_theme_colors(&presentation)?;
|
||||
|
||||
let mut render = PdfRender::new(self.dimensions, output_directory);
|
||||
let mut render = ExportRenderer::new(self.dimensions.clone(), output_directory, renderer);
|
||||
Self::log("waiting for images to be generated and code to be executed, if any...")?;
|
||||
Self::render_async_images(&mut presentation);
|
||||
|
||||
@ -143,10 +131,32 @@ impl<'a> Exporter<'a> {
|
||||
}
|
||||
Self::log("invoking weasyprint...")?;
|
||||
|
||||
Ok(render)
|
||||
}
|
||||
|
||||
/// Export the given presentation into PDF.
|
||||
pub fn export_pdf(
|
||||
mut self,
|
||||
presentation_path: &Path,
|
||||
output_directory: OutputDirectory,
|
||||
output_path: Option<&Path>,
|
||||
) -> Result<(), ExportError> {
|
||||
println!(
|
||||
"exporting using rows={}, columns={}, width={}, height={}",
|
||||
self.dimensions.rows, self.dimensions.columns, self.dimensions.width, self.dimensions.height
|
||||
);
|
||||
|
||||
println!("checking for weasyprint...");
|
||||
Self::validate_weasyprint_exists()?;
|
||||
Self::log("weasyprint installation found")?;
|
||||
|
||||
let render = self.build_renderer(presentation_path, output_directory, Renderer::Pdf)?;
|
||||
|
||||
let pdf_path = match output_path {
|
||||
Some(path) => path.to_path_buf(),
|
||||
None => presentation_path.with_extension("pdf"),
|
||||
};
|
||||
|
||||
render.generate(&pdf_path)?;
|
||||
|
||||
execute!(
|
||||
@ -158,6 +168,36 @@ impl<'a> Exporter<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Export the given presentation into HTML.
|
||||
pub fn export_html(
|
||||
mut self,
|
||||
presentation_path: &Path,
|
||||
output_directory: OutputDirectory,
|
||||
output_path: Option<&Path>,
|
||||
) -> Result<(), ExportError> {
|
||||
println!(
|
||||
"exporting using rows={}, columns={}, width={}, height={}",
|
||||
self.dimensions.rows, self.dimensions.columns, self.dimensions.width, self.dimensions.height
|
||||
);
|
||||
|
||||
let render = self.build_renderer(presentation_path, output_directory, Renderer::Html)?;
|
||||
|
||||
let output_path = match output_path {
|
||||
Some(path) => path.to_path_buf(),
|
||||
None => presentation_path.with_extension("html"),
|
||||
};
|
||||
|
||||
render.generate(&output_path)?;
|
||||
|
||||
execute!(
|
||||
io::stdout(),
|
||||
PrintStyledContent(
|
||||
format!("output file is at {}\n", output_path.display()).stylize().with(Color::Green.into())
|
||||
)
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_async_images(presentation: &mut Presentation) {
|
||||
let poller = Poller::launch();
|
||||
let mut pollables = Vec::new();
|
||||
|
@ -124,17 +124,29 @@ impl ContentManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PdfRender {
|
||||
pub(crate) enum Renderer {
|
||||
Pdf,
|
||||
Html,
|
||||
}
|
||||
|
||||
pub(crate) struct ExportRenderer {
|
||||
content_manager: ContentManager,
|
||||
output_type: Renderer,
|
||||
dimensions: WindowSize,
|
||||
html_body: String,
|
||||
background_color: Option<String>,
|
||||
}
|
||||
|
||||
impl PdfRender {
|
||||
pub(crate) fn new(dimensions: WindowSize, output_directory: OutputDirectory) -> Self {
|
||||
impl ExportRenderer {
|
||||
pub(crate) fn new(dimensions: WindowSize, output_directory: OutputDirectory, output_type: Renderer) -> Self {
|
||||
let image_manager = ContentManager::new(output_directory);
|
||||
Self { content_manager: image_manager, dimensions, html_body: "".to_string(), background_color: None }
|
||||
Self {
|
||||
content_manager: image_manager,
|
||||
dimensions,
|
||||
html_body: "".to_string(),
|
||||
background_color: None,
|
||||
output_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn process_slide(&mut self, slide: Slide) -> Result<(), ExportError> {
|
||||
@ -154,24 +166,9 @@ impl PdfRender {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn generate(self, pdf_path: &Path) -> Result<(), ExportError> {
|
||||
pub(crate) fn generate(self, output_path: &Path) -> Result<(), ExportError> {
|
||||
let html_body = &self.html_body;
|
||||
let script = include_str!("script.js");
|
||||
let html = format!(
|
||||
r#"
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
{html_body}
|
||||
<script>
|
||||
{script}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>"#
|
||||
);
|
||||
let width = (self.dimensions.columns as f64 * FONT_SIZE as f64 * FONT_SIZE_WIDTH).ceil();
|
||||
let height = self.dimensions.rows * LINE_HEIGHT;
|
||||
let background_color = self.background_color.unwrap_or_else(|| "black".into());
|
||||
@ -220,19 +217,42 @@ impl PdfRender {
|
||||
width: {width}px;
|
||||
}}"
|
||||
);
|
||||
let html = format!(
|
||||
r#"
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
{css}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{html_body}
|
||||
<script>
|
||||
{script}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>"#
|
||||
);
|
||||
|
||||
let html_path = self.content_manager.persist_file("index.html", html.as_bytes())?;
|
||||
let css_path = self.content_manager.persist_file("styles.css", css.as_bytes())?;
|
||||
ThirdPartyTools::weasyprint(&[
|
||||
"-s",
|
||||
css_path.to_string_lossy().as_ref(),
|
||||
"--presentational-hints",
|
||||
"-e",
|
||||
"utf8",
|
||||
html_path.to_string_lossy().as_ref(),
|
||||
pdf_path.to_string_lossy().as_ref(),
|
||||
])
|
||||
.run()?;
|
||||
|
||||
match self.output_type {
|
||||
Renderer::Pdf => {
|
||||
ThirdPartyTools::weasyprint(&[
|
||||
"--presentational-hints",
|
||||
"-e",
|
||||
"utf8",
|
||||
html_path.to_string_lossy().as_ref(),
|
||||
output_path.to_string_lossy().as_ref(),
|
||||
])
|
||||
.run()?;
|
||||
}
|
||||
Renderer::Html => {
|
||||
fs::write(output_path, html.as_bytes())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
18
src/main.rs
18
src/main.rs
@ -68,15 +68,19 @@ struct Cli {
|
||||
path: Option<PathBuf>,
|
||||
|
||||
/// Export the presentation as a PDF rather than displaying it.
|
||||
#[clap(short, long)]
|
||||
#[clap(short, long, group = "export")]
|
||||
export_pdf: bool,
|
||||
|
||||
/// Export the presentation as a HTML rather than displaying it.
|
||||
#[clap(short, long, group = "export")]
|
||||
export_html: bool,
|
||||
|
||||
/// The path in which to store temporary files used when exporting.
|
||||
#[clap(long, requires = "export_pdf")]
|
||||
#[clap(long, requires = "export")]
|
||||
export_temporary_path: Option<PathBuf>,
|
||||
|
||||
/// The output path for the exported PDF.
|
||||
#[clap(short = 'o', long = "output", requires = "export_pdf")]
|
||||
#[clap(short = 'o', long = "output", requires = "export")]
|
||||
export_output: Option<PathBuf>,
|
||||
|
||||
/// Generate a JSON schema for the configuration file.
|
||||
@ -401,7 +405,7 @@ fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let parser = MarkdownParser::new(&arena);
|
||||
let validate_overflows =
|
||||
overflow_validation_enabled(&present_mode, &config.defaults.validate_overflows) || cli.validate_overflows;
|
||||
if cli.export_pdf {
|
||||
if cli.export_pdf || cli.export_html {
|
||||
let dimensions = match config.export.dimensions {
|
||||
Some(dimensions) => WindowSize {
|
||||
rows: dimensions.rows,
|
||||
@ -426,7 +430,11 @@ fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Some(path) => OutputDirectory::external(path),
|
||||
None => OutputDirectory::temporary(),
|
||||
}?;
|
||||
exporter.export_pdf(&path, output_directory, cli.export_output.as_deref())?;
|
||||
if cli.export_pdf {
|
||||
exporter.export_pdf(&path, output_directory, cli.export_output.as_deref())?;
|
||||
} else {
|
||||
exporter.export_html(&path, output_directory, cli.export_output.as_deref())?;
|
||||
}
|
||||
} else {
|
||||
let SpeakerNotesComponents { events_listener, events_publisher } =
|
||||
SpeakerNotesComponents::new(&cli, &config, &path)?;
|
||||
|
Loading…
x
Reference in New Issue
Block a user