Chart text without a colour of its own takes the theme's text colour

A chart's title, axis labels, legend and data labels were drawn in a
fixed grey (0x595959) whenever `text_color` was unset, which is
near-invisible on a dark theme. The renderer now resolves the theme's
tx1 instead, so a chart is readable by default on Nocturne as well as
on Harbor; an explicit colour still wins.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Zach Berry
2026-10-23 19:57:50 -05:00
co-authored by Claude Fable 5.1
parent 1b03ae95de
commit be9bf48eb2
2 changed files with 33 additions and 1 deletions
+3 -1
View File
@@ -207,7 +207,9 @@ pub fn draw(ctx: &mut RenderContext, rctx: &Ctx, c: &Chart, m: Affine, w: f64, h
#[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 the theme's text colour, 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(&ColorRef::scheme(SchemeSlot::Tx1)), None);
let grid_col = Rgba::rgb(0xD9, 0xD9, 0xD9);
if let Some(f) = &c.chart_fill {
crate::paint::fill_path(
+30
View File
@@ -2717,3 +2717,33 @@ 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 the theme's: light on Nocturne's dark slides
/// (it used to be a fixed grey, near-invisible there), dark 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));
let dark_ink = count(&light_titled, &|l| l < 80) as i64 - count(&light_bare, &|l| l < 80) as i64;
assert!(dark_ink > 100, "the title is dark on a light theme (dark pixels added: {dark_ink})");
}