Merge pull request #1044 from vbasky/fix/927-vertical-underline

Underline vertical graphic text
This commit is contained in:
Brandon Thomas committed 2026-10-11 19:06:56 -04:00
commit 8a69dc6047
2 files changed
+92

No files matched your search

@@ -0,0 +1,59 @@
//! Horizontal vs vertical graphic text with underline (letter A, 60 px).
use filmcraft_geom::{Affine, Vec2};
use filmcraft_project::graphic::{new_text_layer, new_vertical_text_layer};
use filmcraft_project::{EffectInstance, ParamValue};
use filmcraft_render::graphic_clip::render_graphic_layers;
use filmcraft_render::image::Image;
use filmcraft_time::Tick;
fn set(e: &mut EffectInstance, k: &str, v: ParamValue) {
e.params.get_mut(k).unwrap_or_else(|| panic!("{k}")).value = v;
}
fn draw(layer: EffectInstance) -> Image {
let mut img = Image::new(320, 200);
render_graphic_layers(&[layer], Tick::ZERO, (320, 200), &Affine::IDENTITY, &mut img);
img
}
fn ink_box(img: &Image) -> [usize; 4] {
let mut b: Option<[usize; 4]> = None;
for y in 0..img.h {
for x in 0..img.w {
if img.get(x, y)[3] > 0.4 {
b = Some(match b {
Some(a) => [a[0].min(x), a[1].min(y), a[2].max(x), a[3].max(y)],
None => [x, y, x, y],
});
}
}
}
b.expect("ink")
}
fn count(img: &Image) -> usize {
(0..img.h).flat_map(|y| (0..img.w).map(move |x| (x, y))).filter(|&(x, y)| img.get(x, y)[3] > 0.4).count()
}
#[test]
fn underline_horizontal_vs_vertical_letter_a() {
let pos = Vec2::new(80.0, 100.0);
let mut h = new_text_layer("A", pos, 60.0);
let h_plain = draw(h.clone());
set(&mut h, "underline", ParamValue::Bool(true));
let h_ul = draw(h);
let mut v = new_vertical_text_layer("A", Vec2::new(200.0, 100.0), 60.0);
let v_plain = draw(v.clone());
set(&mut v, "underline", ParamValue::Bool(true));
let v_ul = draw(v);
assert!(count(&h_ul) > count(&h_plain) + 20, "horizontal underline adds ink");
assert!(count(&v_ul) > count(&v_plain) + 20, "vertical underline adds ink");
let hb = ink_box(&h_ul);
let hp = ink_box(&h_plain);
let vb = ink_box(&v_ul);
let vp = ink_box(&v_plain);
assert!(hb[3] > hp[3] + 1, "horizontal underline sits below the letter: plain {hp:?} ul {hb:?}");
assert!(vb[2] > vp[2] + 1, "vertical underline sits to the right of the letter: plain {vp:?} ul {vb:?}");
}
+33
View File
@@ -724,6 +724,7 @@ fn layout_vertical(text: &str, sty: &Styles, para: &ParagraphStyle, col_w: f32,
let row = (pm.ascent + pm.descent).max(style.size * 0.5) + style.tracking * style.size / 1000.0;
let flat = ParagraphStyle { vertical: true, ..Default::default() };
lay.vertical = true;
let rich = sty.rich();
let mut base = 0usize;
let mut ncols = 0usize;
for (c, p) in text.split('\n').enumerate() {
@@ -761,6 +762,21 @@ fn layout_vertical(text: &str, sty: &Styles, para: &ParagraphStyle, col_w: f32,
}
g
}));
// Underline runs down the column, to the right of the cell (the "below" side of vertical-rl).
let y_top = baseline - pl.ascent;
if rich {
for (_x0, x1, si) in &pl.underlines {
let st = sty.list[*si as usize];
let um = fonts::face(sty.prim[*si as usize].face).metrics(st.size);
let x = *x1 + shift - st.baseline_shift + um.underline_pos;
lay.underlines.push([x, y_top, x + um.underline_thickness, y_top + row]);
lay.underline_runs.push(*si);
}
} else if style.underline && pl.content > 0.0 {
let x = shift + pl.left_trim + pl.content + pm.underline_pos;
lay.underlines.push([x, y_top, x + pm.underline_thickness, y_top + row]);
lay.underline_runs.push(0);
}
lay.lines.push(Line {
range: pl.range,
baseline,
@@ -998,6 +1014,23 @@ mod tests {
assert_eq!(h.lines.len(), 2);
}
#[test]
fn vertical_underline_runs_down_the_column() {
let s = TextStyle { underline: true, ..st(60.0) };
let h = layout("A", &s, &ParagraphStyle::default());
let v = layout("A", &s, &ParagraphStyle { vertical: true, ..Default::default() });
assert_eq!(h.underlines.len(), 1);
assert_eq!(v.underlines.len(), 1, "vertical cells get an underline");
let [hx0, hy0, hx1, hy1] = h.underlines[0];
let [vx0, vy0, vx1, vy1] = v.underlines[0];
assert!(hx1 - hx0 > hy1 - hy0, "horizontal underline is a wide strip");
assert!(vy1 - vy0 > vx1 - vx0, "vertical underline is a tall strip");
let runs = [StyleRun { range: 0..1, style: s.clone() }];
let rich = layout_rich("A", &st(60.0), &runs, &ParagraphStyle { vertical: true, ..Default::default() });
assert_eq!(rich.underlines.len(), 1);
assert_eq!(rich.underline_runs, vec![1]);
}
#[test]
fn vertical_cells_keep_combining_marks_with_their_base() {
let style = st(32.0);