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
2 changes: 2 additions & 0 deletions docs/src/features/code/latex.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,5 @@ typst:
horizontal_margin: 2
vertical_margin: 2
```

You can also make use of an alpha channel for typst colours, e.g. `background: ff0000aa`.
3 changes: 3 additions & 0 deletions src/export/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ pub(crate) fn color_to_html(color: &Color) -> String {
Color::White => "#ffffff".into(),
Color::Grey => "#808080".into(),
Color::Rgb { r, g, b } => format!("#{r:02x}{g:02x}{b:02x}"),
Color::Rgba { r, g, b, a } => format!("#{r:02x}{g:02x}{b:02x}{a:02x}"),
}
}

Expand All @@ -105,6 +106,8 @@ mod test {
)]
#[case::foreground_color(TextStyle::default().fg_color(Color::new(1,2,3)), "color: #010203")]
#[case::background_color(TextStyle::default().bg_color(Color::new(1,2,3)), "background-color: #010203")]
#[case::foreground_color_alpha(TextStyle::default().fg_color(Color::Rgba {r: 1, g: 2, b: 3, a:4 }), "color: #01020304")]
#[case::background_color_alpha(TextStyle::default().bg_color(Color::Rgba { r: 1, g: 2, b: 3, a: 4 }), "background-color: #01020304")]
#[case::font_size(TextStyle::default().size(3), "font-size: 6px")]
fn html_text(#[case] style: TextStyle, #[case] expected_style: &str) {
let html_text = HtmlText::new("", &style, FontSize::Pixels(2));
Expand Down
27 changes: 27 additions & 0 deletions src/markdown/text_style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ pub(crate) enum Color {
White,
Grey,
Rgb { r: u8, g: u8, b: u8 },
Rgba { r: u8, g: u8, b: u8, a: u8 },
Copy link
Owner

Choose a reason for hiding this comment

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

I don't particularly like this approach since now any color can have an alpha component, but this has no effect anywhere except in the typst case. I think a better approach is to introduce a tiny RgbaColor type that's parseable from hex and use that inside the typst style config for its background color.

}

impl Color {
Expand Down Expand Up @@ -346,6 +347,15 @@ impl Color {
pub(crate) fn as_rgb(&self) -> Option<(u8, u8, u8)> {
match self {
Self::Rgb { r, g, b } => Some((*r, *g, *b)),
Self::Rgba { r, g, b, .. } => Some((*r, *g, *b)),
_ => None,
}
}

pub(crate) fn as_rgba(&self) -> Option<(u8, u8, u8, u8)> {
match self {
Self::Rgb { r, g, b } => Some((*r, *g, *b, u8::MAX)),
Self::Rgba { r, g, b, a } => Some((*r, *g, *b, *a)),
_ => None,
}
}
Expand Down Expand Up @@ -387,6 +397,7 @@ impl From<Color> for crossterm::style::Color {
Color::White => C::White,
Color::Grey => C::Grey,
Color::Rgb { r, g, b } => C::Rgb { r, g, b },
Color::Rgba { r, g, b, .. } => C::Rgb { r, g, b },
}
}
}
Expand Down Expand Up @@ -518,4 +529,20 @@ mod tests {
let attrs: Vec<_> = style.iter_attributes().collect();
assert_eq!(attrs, expected);
}

#[rstest]
#[case::constant(Color::Green, None)]
#[case::rgb(Color::Rgb { r: 12, g: 38, b: 42 }, Some((12, 38, 42)))]
#[case::rgba(Color::Rgba { r: 12, g: 38, b: 42, a: 72 }, Some((12, 38, 42)))]
fn as_rgb(#[case] color: Color, #[case] expected: Option<(u8, u8, u8)>) {
assert_eq!(color.as_rgb(), expected);
}

#[rstest]
#[case::constant(Color::Green, None)]
#[case::rgb(Color::Rgb { r: 12, g: 38, b: 42 }, Some((12, 38, 42, 255)))]
#[case::rgba(Color::Rgba { r: 12, g: 38, b: 42, a: 72 }, Some((12, 38, 42, 72)))]
fn as_rgba(#[case] color: Color, #[case] expected: Option<(u8, u8, u8, u8)>) {
assert_eq!(color.as_rgba(), expected);
}
}
16 changes: 13 additions & 3 deletions src/theme/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -943,12 +943,18 @@ impl FromStr for RawColor {
// Fallback to hex-encoded rgb
_ => {
let hex = match input.len() {
6 => input.to_string(),
3 => input.chars().flat_map(|c| [c, c]).collect::<String>(),
6 => input.to_string(),
8 => input.to_string(),
len => return Err(ParseColorError::InvalidHexLength(len)),
};
let values = <[u8; 3]>::from_hex(hex)?;
Color::Rgb { r: values[0], g: values[1], b: values[2] }.into()
if hex.len() == 6 {
let values = <[u8; 3]>::from_hex(hex)?;
Color::Rgb { r: values[0], g: values[1], b: values[2] }.into()
} else {
let values = <[u8; 4]>::from_hex(hex)?;
Color::Rgba { r: values[0], g: values[1], b: values[2], a: values[3] }.into()
}
}
};
Ok(output)
Expand All @@ -960,6 +966,7 @@ impl fmt::Display for RawColor {
use Color::*;
match self {
Self::Color(Rgb { r, g, b }) => write!(f, "{}", hex::encode([*r, *g, *b])),
Self::Color(Rgba { r, g, b, a }) => write!(f, "{}", hex::encode([*r, *g, *b, *a])),
Self::Color(Black) => write!(f, "black"),
Self::Color(White) => write!(f, "white"),
Self::Color(Grey) => write!(f, "grey"),
Expand Down Expand Up @@ -1058,6 +1065,9 @@ mod test {

let short_color: RawColor = "ded".parse().unwrap();
assert_eq!(short_color.to_string(), "ddeedd");

let rgba_color: RawColor = "beefff42".parse().unwrap();
assert_eq!(rgba_color.to_string(), "beefff42");
}

#[rstest]
Expand Down
21 changes: 19 additions & 2 deletions src/third_party.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,8 @@ impl Worker {
}

fn as_typst_color(color: &Color) -> Result<String, ThirdPartyRenderError> {
match color.as_rgb() {
Some((r, g, b)) => Ok(format!("rgb(\"#{r:02x}{g:02x}{b:02x}\")")),
match color.as_rgba() {
Some((r, g, b, a)) => Ok(format!("rgb(\"#{r:02x}{g:02x}{b:02x}{a:02x}\")")),
None => Err(ThirdPartyRenderError::UnsupportedColor(RawColor::from(*color).to_string())),
}
}
Expand Down Expand Up @@ -429,3 +429,20 @@ impl Pollable for OperationPollable {
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn as_typst_color() {
let rgb = Worker::as_typst_color(&Color::Rgb { r: 19, g: 57, b: 72 });
assert!(rgb.is_ok());
assert_eq!("rgb(\"#133948ff\")".to_string(), rgb.unwrap());
let rgba = Worker::as_typst_color(&Color::Rgba { r: 102, g: 2, b: 37, a: 24 });
assert!(rgba.is_ok());
assert_eq!("rgb(\"#66022518\")".to_string(), rgba.unwrap());
let err = Worker::as_typst_color(&Color::Red);
assert!(err.is_err());
}
}