diff --git a/src/brush_paint.rs b/src/brush_paint.rs index de89fed8..0a21b6a5 100644 --- a/src/brush_paint.rs +++ b/src/brush_paint.rs @@ -939,7 +939,13 @@ impl Grid { .get(i).copied().unwrap_or((0.0, 1.0)); match best_endpoint { None => best_endpoint = Some(((ex, ey), init_dir)), - Some(((bx_e, by_e), _)) if ey < by_e || (ey == by_e && ex < bx_e) => { + // Bottommost first, leftmost tiebreaker. For most Latin + // letters the natural pen-down anchor is at the bottom + // (M/W/V/U feet, A's foot, vertical-stem letters' base). + // Top-of-glyph endpoints are still candidates — they + // just lose to a bottom one when both exist in the + // same component. + Some(((bx_e, by_e), _)) if ey > by_e || (ey == by_e && ex < bx_e) => { best_endpoint = Some(((ex, ey), init_dir)); } _ => {} @@ -1690,6 +1696,44 @@ mod tests { } } + /// Print the skeleton endpoints + first-stroke start for one letter. + /// Run as: cargo test --release --lib paint_diagnose_endpoints -- --ignored --nocapture + /// Pick char + scale via env: PD_CHAR=M PD_MM=8 PD_DPI=425 PD_THICK=9 + #[test] + #[ignore] + fn paint_diagnose_endpoints() { + let ch: char = std::env::var("PD_CHAR").ok().and_then(|s| s.chars().next()).unwrap_or('M'); + let font_mm: f32 = std::env::var("PD_MM").ok().and_then(|s| s.parse().ok()).unwrap_or(8.0); + let dpi: u32 = std::env::var("PD_DPI").ok().and_then(|s| s.parse().ok()).unwrap_or(425); + let thick: u32 = std::env::var("PD_THICK").ok().and_then(|s| s.parse().ok()).unwrap_or(9); + let hulls = rasterize_letter_at(ch, font_mm, dpi, thick); + let main = match hulls.iter().max_by_key(|h| h.area) { + Some(h) => h, None => { println!("no hull"); return; } + }; + let h = get_or_compute_hull_data(main); + println!("\n=== '{}' @ {}mm/{}dpi/{}px ===", ch, font_mm, dpi, thick); + println!("bbox: x [{}, {}], y [{}, {}] w={} h={}", + main.bounds.x_min, main.bounds.x_max, + main.bounds.y_min, main.bounds.y_max, + h.width, h.height); + println!("skeleton endpoints ({}):", h.skel_endpoints.len()); + for (i, &(ex, ey)) in h.skel_endpoints.iter().enumerate() { + let d = h.skel_endpoints_init_dir.get(i).copied().unwrap_or((0.0, 0.0)); + println!(" #{} pos=({}, {}) init_dir=({:+.2}, {:+.2})", i, ex, ey, d.0, d.1); + } + // Run paint_fill_debug and report the first stroke's start. + let dbg = paint_fill_debug(main, &PaintParams::default()); + println!("brush_radius = {:.2} px", dbg.brush_radius); + println!("first stroke starts: {:?}", dbg.start_points.first()); + println!("first walk init_dir: {:?}", dbg.walks.first().map(|w| w.init_dir)); + println!("strokes: {}", dbg.strokes.len()); + for (i, s) in dbg.strokes.iter().enumerate().take(6) { + if s.is_empty() { continue; } + println!(" stroke #{}: {} pts, start ({:.1}, {:.1}), end ({:.1}, {:.1})", + i, s.len(), s[0].0, s[0].1, s.last().unwrap().0, s.last().unwrap().1); + } + } + #[test] fn paint_letter_I_is_one_stroke() { let hulls = rasterize_letter_at('I', 8.0, 200, 4);