use std::sync::Arc;
use hephaestus::backend::vello::VelloRenderer;
use hephaestus::color::{rgb8, Color};
use hephaestus::composition::{Composition, Patch, Span};
use hephaestus::geometry::Size;
use hephaestus::plot::chrome::axis::{Axis, AxisPlacement};
use hephaestus::plot::value::Value;
use hephaestus::plot::{scale, Plot, PlotComposition, RectGeom, TextFitGeom, TextGeom};
use hephaestus::scales::chrome::AxisSide;
use hephaestus::scene::SceneBuilder;
use hephaestus::Renderer;
fn main() {
let (w, h) = (1200u32, 600u32);
let dpi = 96.0;
let comp = || Composition::empty(1, 1).place(1, 1, Span::cell(), Patch::new("panel"));
let mut renderer = VelloRenderer::new().expect("vello renderer init");
let bg: Color = rgb8(248, 248, 252);
render_aspect_grid(&mut renderer, &comp, w, h, dpi, bg);
render_clip(&mut renderer, &comp, w, h, dpi, bg);
render_justify(&mut renderer, &comp, w, h, dpi, bg);
}
fn render_aspect_grid(
renderer: &mut VelloRenderer,
comp: &impl Fn() -> Composition,
w: u32,
h: u32,
dpi: f64,
bg: Color,
) {
let ink = rgb8(35, 40, 60);
let text_ink = rgb8(80, 50, 30);
let xs0 = [0.04_f64, 0.24, 0.44, 0.62, 0.80];
let xs1 = [0.20_f64, 0.42, 0.58, 0.74, 0.90];
let ys0 = [0.40_f64, 0.30, 0.20, 0.10, 0.05];
let ys1 = [0.60_f64, 0.70, 0.80, 0.90, 0.95];
let mut plot = Plot::new(&comp(), "panel")
.title("TextFitGeom — same text fitted into rects of varying aspect ratio")
.bind("x", "x_axis")
.bind("y", "y_axis");
plot.add_geom(
RectGeom::builder()
.set("x", xs0.to_vec())
.set("x2", xs1.to_vec())
.set("y", ys0.to_vec())
.set("y2", ys1.to_vec())
.set("x_band", vec![0.0_f64; 5])
.set("x2_band", vec![0.0_f64; 5])
.set("stroke", rgb8(160, 170, 200))
.set("linewidth", 1.0_f64)
.build(),
);
plot.add_geom(
TextFitGeom::builder()
.set("x", xs0.to_vec())
.set("x2", xs1.to_vec())
.set("y", ys0.to_vec())
.set("y2", ys1.to_vec())
.set(
"text",
vec!["Fit me", "Fit me", "Fit me", "Fit me", "Fit me"],
)
.set("weight", 600.0_f64)
.set("fill", text_ink)
.set("min_font_size", 6.0_f64)
.set("max_font_size", 72.0_f64)
.set("justify_x", vec!["center"; 5])
.set("justify_y", vec!["center"; 5])
.build(),
);
let _ = ink;
plot.add_axis(Axis::rail(
"x_axis",
AxisPlacement::Cartesian(AxisSide::Bottom),
));
plot.add_axis(Axis::rail(
"y_axis",
AxisPlacement::Cartesian(AxisSide::Left),
));
let mut view = PlotComposition::new(&comp())
.add_scale("x_axis", scale::continuous(0.0..=1.0))
.add_scale("y_axis", scale::continuous(0.0..=1.0))
.with_plot(plot);
render_to(
renderer,
&mut view,
w,
h,
dpi,
bg,
"examples/text_fit_1_aspect_grid.png",
);
}
fn render_clip(
renderer: &mut VelloRenderer,
comp: &impl Fn() -> Composition,
w: u32,
h: u32,
dpi: f64,
bg: Color,
) {
let xs0 = [0.10_f64, 0.55];
let xs1 = [0.45_f64, 0.65];
let ys0 = [0.35_f64, 0.35];
let ys1 = [0.65_f64, 0.65];
let mut plot = Plot::new(&comp(), "panel")
.title("TextFitGeom — fit (left) vs clip-on-overflow (right)")
.bind("x", "x_axis")
.bind("y", "y_axis");
plot.add_geom(
RectGeom::builder()
.set("x", xs0.to_vec())
.set("x2", xs1.to_vec())
.set("y", ys0.to_vec())
.set("y2", ys1.to_vec())
.set("x_band", vec![0.0_f64; 2])
.set("x2_band", vec![0.0_f64; 2])
.set("stroke", rgb8(180, 100, 100))
.set("linewidth", 1.5_f64)
.build(),
);
plot.add_geom(
TextFitGeom::builder()
.set("x", xs0.to_vec())
.set("x2", xs1.to_vec())
.set("y", ys0.to_vec())
.set("y2", ys1.to_vec())
.set(
"text",
vec!["This fits naturally inside the rect", "UnbreakableLongWord"],
)
.set("weight", 500.0_f64)
.set("fill", rgb8(30, 30, 30))
.set("min_font_size", vec![6.0_f64, 24.0])
.set("max_font_size", vec![48.0_f64, 28.0])
.set("justify_x", vec!["center"; 2])
.set("justify_y", vec!["center"; 2])
.build(),
);
plot.add_axis(Axis::rail(
"x_axis",
AxisPlacement::Cartesian(AxisSide::Bottom),
));
plot.add_axis(Axis::rail(
"y_axis",
AxisPlacement::Cartesian(AxisSide::Left),
));
let mut view = PlotComposition::new(&comp())
.add_scale("x_axis", scale::continuous(0.0..=1.0))
.add_scale("y_axis", scale::continuous(0.0..=1.0))
.with_plot(plot);
render_to(
renderer,
&mut view,
w,
h,
dpi,
bg,
"examples/text_fit_2_clip.png",
);
}
fn render_justify(
renderer: &mut VelloRenderer,
comp: &impl Fn() -> Composition,
w: u32,
h: u32,
dpi: f64,
bg: Color,
) {
let justify_choices = ["start", "center", "end"];
let mut xs0: Vec<f64> = Vec::new();
let mut xs1: Vec<f64> = Vec::new();
let mut ys0: Vec<f64> = Vec::new();
let mut ys1: Vec<f64> = Vec::new();
let mut justify_x: Vec<&'static str> = Vec::new();
let mut justify_y: Vec<&'static str> = Vec::new();
let mut labels: Vec<String> = Vec::new();
let cell_w = 0.27_f64;
let cell_h = 0.27_f64;
let pad_x = 0.05_f64;
let pad_y = 0.05_f64;
for (col, jx) in justify_choices.iter().enumerate() {
for (row, jy) in justify_choices.iter().enumerate() {
let x0 = pad_x + col as f64 * (cell_w + pad_x);
let x1 = x0 + cell_w;
let y_top = 1.0 - pad_y - row as f64 * (cell_h + pad_y);
let y_bot = y_top - cell_h;
xs0.push(x0);
xs1.push(x1);
ys0.push(y_bot);
ys1.push(y_top);
justify_x.push(jx);
justify_y.push(jy);
labels.push("Short\nlabel".to_string());
}
}
let _ = Arc::<str>::from(""); let _ = Value::Null;
let mut plot = Plot::new(&comp(), "panel")
.title("TextFitGeom — justify_x × justify_y (multi-line short text)")
.bind("x", "x_axis")
.bind("y", "y_axis");
plot.add_geom(
RectGeom::builder()
.set("x", xs0.clone())
.set("x2", xs1.clone())
.set("y", ys0.clone())
.set("y2", ys1.clone())
.set("x_band", vec![0.0_f64; 9])
.set("x2_band", vec![0.0_f64; 9])
.set("stroke", rgb8(180, 180, 200))
.set("linewidth", 1.0_f64)
.build(),
);
plot.add_geom(
TextFitGeom::builder()
.set("x", xs0.clone())
.set("x2", xs1.clone())
.set("y", ys0.clone())
.set("y2", ys1.clone())
.set("text", labels)
.set("weight", 500.0_f64)
.set("fill", rgb8(30, 30, 30))
.set("max_font_size", 18.0_f64)
.set("justify_x", justify_x.clone())
.set("justify_y", justify_y.clone())
.build(),
);
let combo_labels: Vec<String> = justify_x
.iter()
.zip(justify_y.iter())
.map(|(jx, jy)| format!("x={}, y={}", jx, jy))
.collect();
plot.add_geom(
TextGeom::builder()
.set(
"x",
xs0.iter()
.zip(xs1.iter())
.map(|(a, b)| (a + b) * 0.5)
.collect::<Vec<_>>(),
)
.set("y", ys1.clone())
.set("text", combo_labels)
.set("size", 9.0_f64)
.set("fill", rgb8(100, 100, 120))
.set("anchor_x", 0.5_f64)
.set("anchor_y", 0.0_f64)
.set("y_offset", 3.0_f64)
.build(),
);
plot.add_axis(Axis::rail(
"x_axis",
AxisPlacement::Cartesian(AxisSide::Bottom),
));
plot.add_axis(Axis::rail(
"y_axis",
AxisPlacement::Cartesian(AxisSide::Left),
));
let mut view = PlotComposition::new(&comp())
.add_scale("x_axis", scale::continuous(0.0..=1.0))
.add_scale("y_axis", scale::continuous(0.0..=1.0))
.with_plot(plot);
render_to(
renderer,
&mut view,
w,
h,
dpi,
bg,
"examples/text_fit_3_justify.png",
);
}
fn render_to(
renderer: &mut VelloRenderer,
view: &mut PlotComposition,
w: u32,
h: u32,
dpi: f64,
bg: Color,
out_relative: &str,
) {
{
let scene = renderer.scene();
scene.clear();
view.render(scene, Size::new(w as f64, h as f64), dpi);
}
let mut pixels = vec![0u8; (w * h * 4) as usize];
renderer
.render_to_buffer(w, h, bg, &mut pixels)
.expect("render");
let path = std::env::current_dir().unwrap().join(out_relative);
hephaestus::image::write_png(&path, w, h, &pixels).expect("write png");
println!("wrote {}", path.display());
}