mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-29 21:16:57 -06:00
Merge PR #1015: Chart text can be coloured (chart.options textColor, format.color) and follows the theme by default
Conflicts: crates/engine/src/cmd/insert.rs (kept main's apply_bin_options / apply_subtotals_and_bars helpers and added the textColor assignment)
This commit is contained in:
@@ -255,9 +255,19 @@ pub fn draw(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, w: f64, h
|
||||
}
|
||||
}
|
||||
|
||||
/// PowerPoint's default chart text colour: `tx1` with `lumMod` 65000 and `lumOff` 35000, which
|
||||
/// is 595959 on a light theme and a light grey on a dark one.
|
||||
pub fn default_text_color() -> ColorRef {
|
||||
use deckcraft_color::ColorTransform;
|
||||
ColorRef::scheme(SchemeSlot::Tx1).with(ColorTransform::LumMod(65000)).with(ColorTransform::LumOff(35000))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn draw_inner(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, w: f64, h: f64, boxes: Option<&crate::chartex::Boxes>) {
|
||||
let text_col = c.text_color.as_ref().map(|t| rctx.color(t, None)).unwrap_or(Rgba::rgb(0x59, 0x59, 0x59));
|
||||
// Text without a colour of its own takes PowerPoint's default, the theme's text colour at
|
||||
// lumMod 65 % / lumOff 35 % (595959 on a light theme, light grey on a dark one), so a chart
|
||||
// on a dark theme is readable (a fixed grey used to vanish there).
|
||||
let text_col = rctx.color(c.text_color.as_ref().unwrap_or(&default_text_color()), None);
|
||||
let grid_col = Rgba::rgb(0xD9, 0xD9, 0xD9);
|
||||
if let Some(f) = &c.chart_fill {
|
||||
crate::paint::fill_path(
|
||||
@@ -389,7 +399,8 @@ fn draw_inner(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, w: f64,
|
||||
base,
|
||||
Align::Center,
|
||||
Anchor::Middle,
|
||||
Rgba::WHITE,
|
||||
// On the slices themselves: white, unless the chart's text was given a colour.
|
||||
if c.text_color.is_some() { text_col } else { Rgba::WHITE },
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2754,3 +2754,61 @@ fn empty_bullet_is_gray_only_while_editing_and_never_in_exports() {
|
||||
assert!(changed.iter().any(|(px, _)| px[0] == 179 && px[1] == 179 && px[2] == 179));
|
||||
assert!(changed.iter().all(|(px, _)| px[0] >= 179 && px[0] == px[1] && px[1] == px[2]));
|
||||
}
|
||||
|
||||
/// A chart that has no text colour of its own takes PowerPoint's default, the theme's text colour
|
||||
/// at 65 % plus 35 %: light on Nocturne's dark slides (it used to be a fixed grey, near-invisible
|
||||
/// there), 595959 grey on Harbor's light ones.
|
||||
#[test]
|
||||
fn chart_text_follows_the_theme_when_it_has_no_colour_of_its_own() {
|
||||
let draw = |theme: &str, title: Option<&str>| {
|
||||
let mut c = Chart::sample(ChartType::Column);
|
||||
c.title = title.map(String::from);
|
||||
c.legend = None;
|
||||
c.data_labels = false;
|
||||
let sh = Shape { id: ShapeId(630), xfrm: Some(Xfrm::new(0.0, 0.0, 960.0, 540.0)), kind: ShapeKind::Chart(Box::new(c)), ..Default::default() };
|
||||
let mut p = deck_with(vec![sh]);
|
||||
let t = deckcraft_model::theme::builtin_themes().into_iter().find(|t| t.name == theme).expect("theme");
|
||||
std::sync::Arc::make_mut(&mut p.masters[0]).theme = t;
|
||||
render_slide(&p, 0, &RenderOpts { scale: 1.0, ..Default::default() })
|
||||
};
|
||||
let lum = |px: [u8; 4]| (px[0] as u32 * 299 + px[1] as u32 * 587 + px[2] as u32 * 114) / 1000;
|
||||
// Pixels in the title band that pass `f`.
|
||||
let count = |img: &Image, f: &dyn Fn(u32) -> bool| {
|
||||
(200..760u32).flat_map(|x| (4..70u32).map(move |y| (x, y))).filter(|(x, y)| f(lum(img.pixel(*x, *y)))).count()
|
||||
};
|
||||
let (dark_bare, dark_titled) = (draw("Nocturne", None), draw("Nocturne", Some("Quarterly revenue")));
|
||||
assert!(lum(dark_bare.pixel(5, 5)) < 60, "Nocturne's slide is dark: {:?}", dark_bare.pixel(5, 5));
|
||||
let light_ink = count(&dark_titled, &|l| l > 200) as i64 - count(&dark_bare, &|l| l > 200) as i64;
|
||||
assert!(light_ink > 100, "the title is light on a dark theme (light pixels added: {light_ink})");
|
||||
let (light_bare, light_titled) = (draw("Harbor", None), draw("Harbor", Some("Quarterly revenue")));
|
||||
assert!(lum(light_bare.pixel(5, 5)) > 200, "Harbor's slide is light: {:?}", light_bare.pixel(5, 5));
|
||||
// 595959 has luminance 89: dark, but not black.
|
||||
let dark_ink = count(&light_titled, &|l| l < 120) as i64 - count(&light_bare, &|l| l < 120) as i64;
|
||||
assert!(dark_ink > 100, "the title is dark on a light theme (dark pixels added: {dark_ink})");
|
||||
let black_ink = count(&light_titled, &|l| l < 40) as i64 - count(&light_bare, &|l| l < 40) as i64;
|
||||
assert!(black_ink < 20, "the title is PowerPoint's 595959 grey, not black (black pixels added: {black_ink})");
|
||||
}
|
||||
|
||||
/// Pie data labels sit on the slices in white; a colour given to the chart's text (chart.options
|
||||
/// textColor, Font Color on the chart) colours them too, as it does the title and legend.
|
||||
#[test]
|
||||
fn pie_data_labels_take_the_chart_text_colour_when_it_has_one() {
|
||||
let draw = |colour: Option<ColorRef>| {
|
||||
let mut c = Chart::sample(ChartType::Pie);
|
||||
c.title = None;
|
||||
c.legend = None;
|
||||
c.data_labels = true;
|
||||
c.text_color = colour;
|
||||
let sh = Shape { id: ShapeId(632), xfrm: Some(Xfrm::new(0.0, 0.0, 400.0, 400.0)), kind: ShapeKind::Chart(Box::new(c)), ..Default::default() };
|
||||
render_slide(&deck_with(vec![sh]), 0, &RenderOpts { scale: 1.0, ..Default::default() })
|
||||
};
|
||||
let count = |img: &Image, f: &dyn Fn([u8; 4]) -> bool| {
|
||||
(0..400u32).flat_map(|x| (0..400u32).map(move |y| (x, y))).filter(|(x, y)| f(img.pixel(*x, *y))).count()
|
||||
};
|
||||
let red = |p: [u8; 4]| p[0] > 200 && p[1] < 60 && p[2] < 60;
|
||||
let white = |p: [u8; 4]| p[0] > 245 && p[1] > 245 && p[2] > 245;
|
||||
let (plain, coloured) = (draw(None), draw(Some(ColorRef::rgb(deckcraft_color::Rgba::rgb(255, 0, 0)))));
|
||||
assert_eq!(count(&plain, &red), 0, "no red without a text colour");
|
||||
assert!(count(&coloured, &red) > 30, "the labels take the chart's red: {} red pixels", count(&coloured, &red));
|
||||
assert!(count(&plain, &white) > count(&coloured, &white), "the labels were white before");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user