Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion src/presentation/builder/comment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
markdown::elements::{MarkdownElement, SourcePosition},
presentation::builder::{BuildResult, LayoutState, PresentationBuilder, error::InvalidPresentation},
render::operation::{LayoutGrid, RenderOperation},
theme::{Alignment, ElementType},
theme::{Alignment, ElementType, raw::RawColor},
};
use serde::Deserialize;
use std::{fmt, num::NonZeroU8, path::PathBuf, str::FromStr};
Expand Down Expand Up @@ -116,6 +116,12 @@ impl PresentationBuilder<'_, '_> {
self.push_detached_code_execution(handle)?;
return Ok(());
}
CommentCommand::SlideBackgroundColor(color) => {
let color = color
.resolve(&self.theme.palette)
.map_err(|e| self.invalid_presentation(source_position, InvalidPresentation::InvalidColor(e)))?;
self.slide_state.background_color = color;
}
};
// Don't push line breaks for any comments.
self.slide_state.ignore_element_line_break = true;
Expand Down Expand Up @@ -186,6 +192,7 @@ impl PresentationBuilder<'_, '_> {
#[serde(rename_all = "snake_case")]
pub(crate) enum CommentCommand {
Alignment(CommentCommandAlignment),
SlideBackgroundColor(RawColor),
Column(usize),
EndSlide,
FontSize(u8),
Expand Down Expand Up @@ -231,6 +238,7 @@ impl CommentCommand {
format!("<!-- include: file.md -->"),
format!("<!-- speaker_note: Your note here -->"),
format!("<!-- snippet_output: identifier -->"),
format!("<!-- slide_background_color: ff0000 -->"),
]
}
}
Expand Down Expand Up @@ -295,6 +303,14 @@ mod tests {
assert_eq!(parsed, expected);
}

#[rstest]
#[case::hex("slide_background_color: ff0000")]
#[case::named("slide_background_color: red")]
fn background_color_parsing(#[case] input: &str) {
let parsed: CommentCommand = input.parse().expect("deserialization failed");
assert!(matches!(parsed, CommentCommand::SlideBackgroundColor(_)));
}

#[rstest]
#[case::multiline("hello\nworld")]
#[case::many_open_braces("{{{")]
Expand Down Expand Up @@ -762,4 +778,40 @@ hi
let err = Test::new(input).resources_path(path).expect_invalid();
assert!(err.to_string().contains("was already imported"), "{err:?}");
}

#[test]
fn background_color() {
use crate::markdown::text_style::Color;

let input = "
<!-- slide_background_color: ff0000 -->

hello
";
let (lines, styles) = Test::new(input)
.render()
.rows(4)
.columns(10)
.map_background(Color::Rgb { r: 255, g: 0, b: 0 }, 'R')
.into_parts();

// Find the line containing "hello" and verify it has red background
let hello_line_idx = lines.iter().position(|l| l.contains("hello")).expect("hello not found");
assert!(
styles[hello_line_idx].starts_with("RRRRR"),
"Expected text to have red background.\nLines: {:?}\nStyles: {:?}",
lines,
styles
);
}

#[test]
fn background_color_invalid() {
let input = "
<!-- slide_background_color: palette:undefined_color -->

hello
";
Test::new(input).expect_invalid();
}
}
3 changes: 3 additions & 0 deletions src/presentation/builder/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ pub(crate) enum InvalidPresentation {

#[error("snippet id '{0}' already exists")]
SnippetAlreadyExists(String),

#[error("invalid color: {0}")]
InvalidColor(#[from] UndefinedPaletteColorError),
}

#[derive(Clone, Debug)]
Expand Down
18 changes: 17 additions & 1 deletion src/presentation/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -509,8 +509,23 @@ impl<'a, 'b> PresentationBuilder<'a, 'b> {
}

fn terminate_slide(&mut self) {
let operations = mem::take(&mut self.chunk_operations);
let mut operations = mem::take(&mut self.chunk_operations);
let mutators = mem::take(&mut self.chunk_mutators);

// Apply background color to all SetColors operations in the current chunk
// so the entire slide uses the custom background
if let Some(bg_color) = self.slide_state.background_color {
for op in &mut operations {
if let RenderOperation::SetColors(colors) = op {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right. It's possible there's more than the initial SetColors inside a slide. e.g. a <span ... that sets the color.

colors.background = Some(bg_color);
}
}
// Also apply to any previously created chunks (from pause commands)
for chunk in &mut self.slide_chunks {
chunk.apply_background_color(bg_color);
}
}

// Don't allow a last empty pause in slide since it adds nothing
if self.slide_chunks.is_empty() || !Self::is_chunk_empty(&operations) {
self.slide_chunks.push(SlideChunk::new(operations, mutators));
Expand Down Expand Up @@ -600,6 +615,7 @@ struct SlideState {
alignment: Option<Alignment>,
skip_slide: bool,
last_layout_comment: Option<FileSourcePosition>,
background_color: Option<Color>,
}

#[derive(Clone, Debug, Default)]
Expand Down
8 changes: 8 additions & 0 deletions src/presentation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,14 @@ impl SlideChunk {
mutator.apply_all_mutations();
}
}

pub(crate) fn apply_background_color(&mut self, bg_color: crate::markdown::text_style::Color) {
for op in &mut self.operations {
if let RenderOperation::SetColors(colors) = op {
colors.background = Some(bg_color);
}
}
}
}

pub(crate) trait ChunkMutator: Debug {
Expand Down
Loading