Skip to content

Commit

Permalink
Merge pull request #75 from mfontanini/render-formulas
Browse files Browse the repository at this point in the history
Allow rendering typst/latex formulas
  • Loading branch information
mfontanini authored Dec 4, 2023
2 parents d1bf9e5 + 7834766 commit ad45a2e
Show file tree
Hide file tree
Showing 14 changed files with 256 additions and 31 deletions.
29 changes: 26 additions & 3 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::{
Alignment, AuthorPositioning, ElementType, FooterStyle, LoadThemeError, Margin, PresentationTheme,
PresentationThemeSet,
},
typst::{TypstRender, TypstRenderError},
};
use itertools::Itertools;
use serde::Deserialize;
Expand Down Expand Up @@ -59,6 +60,7 @@ pub(crate) struct PresentationBuilder<'a> {
highlighter: CodeHighlighter,
theme: Cow<'a, PresentationTheme>,
resources: &'a mut Resources,
typst: &'a mut TypstRender,
slide_state: SlideState,
footer_context: Rc<RefCell<FooterContext>>,
themes: &'a Themes,
Expand All @@ -71,6 +73,7 @@ impl<'a> PresentationBuilder<'a> {
default_highlighter: CodeHighlighter,
default_theme: &'a PresentationTheme,
resources: &'a mut Resources,
typst: &'a mut TypstRender,
themes: &'a Themes,
options: PresentationBuilderOptions,
) -> Self {
Expand All @@ -82,6 +85,7 @@ impl<'a> PresentationBuilder<'a> {
highlighter: default_highlighter,
theme: Cow::Borrowed(default_theme),
resources,
typst,
slide_state: Default::default(),
footer_context: Default::default(),
themes,
Expand Down Expand Up @@ -154,7 +158,7 @@ impl<'a> PresentationBuilder<'a> {
MarkdownElement::Heading { level, text } => self.push_heading(level, text),
MarkdownElement::Paragraph(elements) => self.push_paragraph(elements)?,
MarkdownElement::List(elements) => self.push_list(elements),
MarkdownElement::Code(code) => self.push_code(code),
MarkdownElement::Code(code) => self.push_code(code)?,
MarkdownElement::Table(table) => self.push_table(table),
MarkdownElement::ThematicBreak => self.push_separator(),
MarkdownElement::Comment { comment, source_position } => self.process_comment(comment, source_position)?,
Expand Down Expand Up @@ -486,7 +490,10 @@ impl<'a> PresentationBuilder<'a> {
self.chunk_operations.push(RenderOperation::RenderLineBreak);
}

fn push_code(&mut self, code: Code) {
fn push_code(&mut self, code: Code) -> Result<(), BuildError> {
if code.attributes.auto_render {
return self.push_rendered_code(code);
}
let (lines, context) = self.highlight_lines(&code);
for line in lines {
self.chunk_operations.push(RenderOperation::RenderDynamic(Rc::new(line)));
Expand All @@ -498,6 +505,18 @@ impl<'a> PresentationBuilder<'a> {
if code.attributes.execute {
self.push_code_execution(code);
}
Ok(())
}

fn push_rendered_code(&mut self, code: Code) -> Result<(), BuildError> {
let image = match code.language {
CodeLanguage::Typst => self.typst.render_typst(&code.contents, &self.theme.typst)?,
CodeLanguage::Latex => self.typst.render_latex(&code.contents, &self.theme.typst)?,
_ => panic!("language {:?} should not be renderable", code.language),
};
let operation = RenderOperation::RenderImage(image);
self.chunk_operations.push(operation);
Ok(())
}

fn highlight_lines(&self, code: &Code) -> (Vec<HighlightedLine>, Rc<RefCell<HighlightContext>>) {
Expand Down Expand Up @@ -919,6 +938,9 @@ pub enum BuildError {

#[error("error parsing command at line {line}: {error}")]
CommandParse { line: usize, error: CommandParseError },

#[error("typst render failed: {0}")]
TypstRender(#[from] TypstRenderError),
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
Expand Down Expand Up @@ -1165,9 +1187,10 @@ mod test {
let highlighter = CodeHighlighter::default();
let theme = PresentationTheme::default();
let mut resources = Resources::new("/tmp");
let mut typst = TypstRender::default();
let options = PresentationBuilderOptions::default();
let themes = Themes::default();
let builder = PresentationBuilder::new(highlighter, &theme, &mut resources, &themes, options);
let builder = PresentationBuilder::new(highlighter, &theme, &mut resources, &mut typst, &themes, options);
builder.build(elements)
}

Expand Down
19 changes: 19 additions & 0 deletions src/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use std::{fs, io, path::Path};
pub struct Config {
#[serde(default)]
pub defaults: DefaultsConfig,

#[serde(default)]
pub typst: TypstConfig,
}

impl Config {
Expand Down Expand Up @@ -33,3 +36,19 @@ pub enum ConfigLoadError {
pub struct DefaultsConfig {
pub theme: Option<String>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct TypstConfig {
#[serde(default = "default_typst_ppi")]
pub ppi: u32,
}

impl Default for TypstConfig {
fn default() -> Self {
Self { ppi: default_typst_ppi() }
}
}

fn default_typst_ppi() -> u32 {
300
}
14 changes: 8 additions & 6 deletions src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
builder::{BuildError, PresentationBuilder, PresentationBuilderOptions, Themes},
markdown::{elements::MarkdownElement, parse::ParseError},
presentation::{Presentation, RenderOperation},
typst::TypstRender,
CodeHighlighter, MarkdownParser, PresentationTheme, Resources,
};
use serde::Serialize;
Expand All @@ -18,8 +19,8 @@ const COMMAND: &str = "presenterm-export";
pub struct Exporter<'a> {
parser: MarkdownParser<'a>,
default_theme: &'a PresentationTheme,
default_highlighter: CodeHighlighter,
resources: Resources,
typst: TypstRender,
themes: Themes,
}

Expand All @@ -28,11 +29,11 @@ impl<'a> Exporter<'a> {
pub fn new(
parser: MarkdownParser<'a>,
default_theme: &'a PresentationTheme,
default_highlighter: CodeHighlighter,
resources: Resources,
typst: TypstRender,
themes: Themes,
) -> Self {
Self { parser, default_theme, default_highlighter, resources, themes }
Self { parser, default_theme, resources, typst, themes }
}

/// Export the given presentation into PDF.
Expand All @@ -59,9 +60,10 @@ impl<'a> Exporter<'a> {
let images = Self::build_image_metadata(&elements, base_path);
let options = PresentationBuilderOptions { allow_mutations: false };
let presentation = PresentationBuilder::new(
self.default_highlighter.clone(),
CodeHighlighter::default(),
self.default_theme,
&mut self.resources,
&mut self.typst,
&self.themes,
options,
)
Expand Down Expand Up @@ -200,10 +202,10 @@ mod test {
let arena = Arena::new();
let parser = MarkdownParser::new(&arena);
let theme = PresentationThemeSet::default().load_by_name("dark").unwrap();
let highlighter = CodeHighlighter::default();
let resources = Resources::new("examples");
let typst = TypstRender::default();
let themes = Themes::default();
let mut exporter = Exporter::new(parser, &theme, highlighter, resources, themes);
let mut exporter = Exporter::new(parser, &theme, resources, typst, themes);
exporter.extract_metadata(content, Path::new(path)).expect("metadata extraction failed")
}

Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub(crate) mod render;
pub(crate) mod resource;
pub(crate) mod style;
pub(crate) mod theme;
pub(crate) mod typst;

pub use crate::{
builder::Themes,
Expand All @@ -26,4 +27,5 @@ pub use crate::{
render::highlighting::{CodeHighlighter, HighlightThemeSet},
resource::Resources,
theme::{LoadThemeError, PresentationTheme, PresentationThemeSet},
typst::TypstRender,
};
10 changes: 5 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use clap::{error::ErrorKind, CommandFactory, Parser};
use comrak::Arena;
use presenterm::{
CodeHighlighter, CommandSource, Config, Exporter, HighlightThemeSet, LoadThemeError, MarkdownParser, PresentMode,
PresentationThemeSet, Presenter, Resources, Themes,
CommandSource, Config, Exporter, HighlightThemeSet, LoadThemeError, MarkdownParser, PresentMode,
PresentationThemeSet, Presenter, Resources, Themes, TypstRender,
};
use std::{
env,
Expand Down Expand Up @@ -108,16 +108,16 @@ fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
};
let arena = Arena::new();
let parser = MarkdownParser::new(&arena);
let default_highlighter = CodeHighlighter::default();
if cli.acknowledgements {
display_acknowledgements();
return Ok(());
}
let path = cli.path.expect("no path");
let resources_path = path.parent().unwrap_or(Path::new("/"));
let resources = Resources::new(resources_path);
let typst = TypstRender::new(config.typst.ppi);
if cli.export_pdf || cli.generate_pdf_metadata {
let mut exporter = Exporter::new(parser, &default_theme, default_highlighter, resources, themes);
let mut exporter = Exporter::new(parser, &default_theme, resources, typst, themes);
if cli.export_pdf {
exporter.export_pdf(&path)?;
} else {
Expand All @@ -126,7 +126,7 @@ fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
}
} else {
let commands = CommandSource::new(&path);
let presenter = Presenter::new(&default_theme, default_highlighter, commands, parser, resources, themes, mode);
let presenter = Presenter::new(&default_theme, commands, parser, resources, typst, themes, mode);
presenter.present(&path)?;
}
Ok(())
Expand Down
13 changes: 10 additions & 3 deletions src/markdown/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ impl CodeBlockParser {
let (language, input) = Self::parse_language(input);
let attributes = Self::parse_attributes(input)?;
if attributes.execute && !language.supports_execution() {
return Err(CodeBlockParseError::ExecutionNotSupported(language));
return Err(CodeBlockParseError::UnsupportedAttribute(language, "execution"));
}
if attributes.auto_render && !language.supports_auto_render() {
return Err(CodeBlockParseError::UnsupportedAttribute(language, "rendering"));
}
Ok((language, attributes))
}
Expand Down Expand Up @@ -69,6 +72,7 @@ impl CodeBlockParser {
"swift" => Swift,
"terraform" => Terraform,
"typescript" | "ts" => TypeScript,
"typst" => Typst,
"xml" => Xml,
"yaml" => Yaml,
"vue" => Vue,
Expand All @@ -90,6 +94,7 @@ impl CodeBlockParser {
match attribute {
Attribute::LineNumbers => attributes.line_numbers = true,
Attribute::Exec => attributes.execute = true,
Attribute::AutoRender => attributes.auto_render = true,
Attribute::HighlightedLines(lines) => attributes.highlight_groups = lines,
};
processed_attributes.push(discriminant);
Expand All @@ -109,6 +114,7 @@ impl CodeBlockParser {
let attribute = match token {
"line_numbers" => Attribute::LineNumbers,
"exec" => Attribute::Exec,
"render" => Attribute::AutoRender,
_ => return Err(CodeBlockParseError::InvalidToken(Self::next_identifier(input).into())),
};
(Some(attribute), &input[token.len() + 1..])
Expand Down Expand Up @@ -197,14 +203,15 @@ pub(crate) enum CodeBlockParseError {
#[error("duplicate attribute: {0}")]
DuplicateAttribute(&'static str),

#[error("language {0:?} does not support execution")]
ExecutionNotSupported(CodeLanguage),
#[error("language {0:?} does not support {1}")]
UnsupportedAttribute(CodeLanguage, &'static str),
}

#[derive(EnumDiscriminants)]
enum Attribute {
LineNumbers,
Exec,
AutoRender,
HighlightedLines(Vec<HighlightGroup>),
}

Expand Down
11 changes: 11 additions & 0 deletions src/markdown/elements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ pub(crate) enum CodeLanguage {
Svelte,
Terraform,
TypeScript,
Typst,
Unknown,
Xml,
Yaml,
Expand All @@ -246,6 +247,10 @@ impl CodeLanguage {
pub(crate) fn supports_execution(&self) -> bool {
matches!(self, Self::Shell(_))
}

pub(crate) fn supports_auto_render(&self) -> bool {
matches!(self, Self::Latex | Self::Typst)
}
}

/// Attributes for code blocks.
Expand All @@ -254,6 +259,12 @@ pub(crate) struct CodeAttributes {
/// Whether the code block is marked as executable.
pub(crate) execute: bool,

/// Whether a code block is marked to be auto rendered.
///
/// An auto rendered piece of code is transformed during parsing, leading to some visual
/// representation of it being shown rather than the original code.
pub(crate) auto_render: bool,

/// Whether the code block should show line numbers.
pub(crate) line_numbers: bool,

Expand Down
10 changes: 6 additions & 4 deletions src/presenter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
},
resource::Resources,
theme::PresentationTheme,
typst::TypstRender,
};
use std::{
collections::HashSet,
Expand All @@ -24,10 +25,10 @@ use std::{
/// This type puts everything else together.
pub struct Presenter<'a> {
default_theme: &'a PresentationTheme,
default_highlighter: CodeHighlighter,
commands: CommandSource,
parser: MarkdownParser<'a>,
resources: Resources,
typst: TypstRender,
mode: PresentMode,
state: PresenterState,
slides_with_pending_widgets: HashSet<usize>,
Expand All @@ -38,19 +39,19 @@ impl<'a> Presenter<'a> {
/// Construct a new presenter.
pub fn new(
default_theme: &'a PresentationTheme,
default_highlighter: CodeHighlighter,
commands: CommandSource,
parser: MarkdownParser<'a>,
resources: Resources,
typst: TypstRender,
themes: Themes,
mode: PresentMode,
) -> Self {
Self {
default_theme,
default_highlighter,
commands,
parser,
resources,
typst,
mode,
state: PresenterState::Empty,
slides_with_pending_widgets: HashSet::new(),
Expand Down Expand Up @@ -187,9 +188,10 @@ impl<'a> Presenter<'a> {
options.allow_mutations = false;
}
let presentation = PresentationBuilder::new(
self.default_highlighter.clone(),
CodeHighlighter::default(),
self.default_theme,
&mut self.resources,
&mut self.typst,
&self.themes,
options,
)
Expand Down
1 change: 1 addition & 0 deletions src/render/highlighting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ impl CodeHighlighter {
Svelte => "svelte",
Terraform => "tf",
TypeScript => "ts",
Typst => "txt",
// default to plain text so we get the same look&feel
Unknown => "txt",
Vue => "vue",
Expand Down
Loading

0 comments on commit ad45a2e

Please sign in to comment.