//! Box Drawing | U+2500...U+257F //! //! //! ─━│┃┄┅┆┇┈┉┊┋┌┍┎┏ //! ┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟ //! ┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯ //! ┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿ //! ╀╁╂╃╄╅╆╇╈╉╊╋╌╍╎╏ //! ═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟ //! ╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╯ //! ╰╱╲╳╴╵╶╷╸╹╺╻╼╽╾╿ //! //! Port of `sprite/draw/box.zig`. #![allow(clippy::needless_update)] use super::common::{Corner, DrawFn, Range, Thickness, hline, hline_middle, vline, vline_middle}; use crate::{ metrics::Metrics, sprite::canvas::{Canvas, CapMode, Color, DrawError, Line, Path, Point, StrokeOptions}, }; pub const RANGES: &[Range] = &[Range { min: 0x2500, max: 0x257f, draw: draw2500_257f as DrawFn, }]; /// Style of a line from an edge to the center. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Style { #[default] None, Light, Heavy, Double, } /// Specification of a traditional intersection-style line/box-drawing char, /// which can have a different style of line from each edge to the center. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Lines { pub up: Style, pub right: Style, pub down: Style, pub left: Style, } impl Lines { pub const NONE: Lines = Lines { up: Style::None, right: Style::None, down: Style::None, left: Style::None, }; } pub fn draw2500_257f( cp: u32, canvas: &mut Canvas, _width: u32, _height: u32, metrics: &Metrics, ) -> Result<(), DrawError> { let light = Thickness::Light.height(metrics.box_thickness); let heavy = Thickness::Heavy.height(metrics.box_thickness); let gap4 = 4u32.max(light); match cp { // '┄' '┅' '┆' '┇' '┈' '┉' '┊' '┋' 0x2504 => dash_horizontal(metrics, canvas, 3, light, gap4), 0x2505 => dash_horizontal(metrics, canvas, 3, heavy, gap4), 0x2506 => dash_vertical(metrics, canvas, 3, light, gap4), 0x2507 => dash_vertical(metrics, canvas, 3, heavy, gap4), 0x2508 => dash_horizontal(metrics, canvas, 4, light, gap4), 0x2509 => dash_horizontal(metrics, canvas, 4, heavy, gap4), 0x250a => dash_vertical(metrics, canvas, 4, light, gap4), 0x250b => dash_vertical(metrics, canvas, 4, heavy, gap4), // '╌' '╍' '╎' '╏' 0x254c => dash_horizontal(metrics, canvas, 2, light, light), 0x254d => dash_horizontal(metrics, canvas, 2, heavy, heavy), 0x254e => dash_vertical(metrics, canvas, 2, light, heavy), 0x254f => dash_vertical(metrics, canvas, 2, heavy, heavy), // '╭' 0x256d => arc(metrics, canvas, Corner::Br, Thickness::Light)?, // '╮' 0x256e => arc(metrics, canvas, Corner::Bl, Thickness::Light)?, // '╯' 0x256f => arc(metrics, canvas, Corner::Tl, Thickness::Light)?, // '╰' 0x2570 => arc(metrics, canvas, Corner::Tr, Thickness::Light)?, // '╱' 0x2571 => light_diagonal_upper_right_to_lower_left(metrics, canvas), // '╲' 0x2572 => light_diagonal_upper_left_to_lower_right(metrics, canvas), // '╳' 0x2573 => light_diagonal_cross(metrics, canvas), // All the remaining codepoints are intersection-style lines. 0x2500 => lines_char( metrics, canvas, Lines { left: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x2501 => lines_char( metrics, canvas, Lines { left: Style::Heavy, right: Style::Heavy, ..Lines::NONE }, ), 0x2502 => lines_char( metrics, canvas, Lines { up: Style::Light, down: Style::Light, ..Lines::NONE }, ), 0x2503 => lines_char( metrics, canvas, Lines { up: Style::Heavy, down: Style::Heavy, ..Lines::NONE }, ), 0x250c => lines_char( metrics, canvas, Lines { down: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x250d => lines_char( metrics, canvas, Lines { down: Style::Light, right: Style::Heavy, ..Lines::NONE }, ), 0x250e => lines_char( metrics, canvas, Lines { down: Style::Heavy, right: Style::Light, ..Lines::NONE }, ), 0x250f => lines_char( metrics, canvas, Lines { down: Style::Heavy, right: Style::Heavy, ..Lines::NONE }, ), 0x2510 => lines_char( metrics, canvas, Lines { down: Style::Light, left: Style::Light, ..Lines::NONE }, ), 0x2511 => lines_char( metrics, canvas, Lines { down: Style::Light, left: Style::Heavy, ..Lines::NONE }, ), 0x2512 => lines_char( metrics, canvas, Lines { down: Style::Heavy, left: Style::Light, ..Lines::NONE }, ), 0x2513 => lines_char( metrics, canvas, Lines { down: Style::Heavy, left: Style::Heavy, ..Lines::NONE }, ), 0x2514 => lines_char( metrics, canvas, Lines { up: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x2515 => lines_char( metrics, canvas, Lines { up: Style::Light, right: Style::Heavy, ..Lines::NONE }, ), 0x2516 => lines_char( metrics, canvas, Lines { up: Style::Heavy, right: Style::Light, ..Lines::NONE }, ), 0x2517 => lines_char( metrics, canvas, Lines { up: Style::Heavy, right: Style::Heavy, ..Lines::NONE }, ), 0x2518 => lines_char( metrics, canvas, Lines { up: Style::Light, left: Style::Light, ..Lines::NONE }, ), 0x2519 => lines_char( metrics, canvas, Lines { up: Style::Light, left: Style::Heavy, ..Lines::NONE }, ), 0x251a => lines_char( metrics, canvas, Lines { up: Style::Heavy, left: Style::Light, ..Lines::NONE }, ), 0x251b => lines_char( metrics, canvas, Lines { up: Style::Heavy, left: Style::Heavy, ..Lines::NONE }, ), 0x251c => lines_char( metrics, canvas, Lines { up: Style::Light, down: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x251d => lines_char( metrics, canvas, Lines { up: Style::Light, down: Style::Light, right: Style::Heavy, ..Lines::NONE }, ), 0x251e => lines_char( metrics, canvas, Lines { up: Style::Heavy, right: Style::Light, down: Style::Light, ..Lines::NONE }, ), 0x251f => lines_char( metrics, canvas, Lines { down: Style::Heavy, right: Style::Light, up: Style::Light, ..Lines::NONE }, ), 0x2520 => lines_char( metrics, canvas, Lines { up: Style::Heavy, down: Style::Heavy, right: Style::Light, ..Lines::NONE }, ), 0x2521 => lines_char( metrics, canvas, Lines { down: Style::Light, right: Style::Heavy, up: Style::Heavy, ..Lines::NONE }, ), 0x2522 => lines_char( metrics, canvas, Lines { up: Style::Light, right: Style::Heavy, down: Style::Heavy, ..Lines::NONE }, ), 0x2523 => lines_char( metrics, canvas, Lines { up: Style::Heavy, down: Style::Heavy, right: Style::Heavy, ..Lines::NONE }, ), 0x2524 => lines_char( metrics, canvas, Lines { up: Style::Light, down: Style::Light, left: Style::Light, ..Lines::NONE }, ), 0x2525 => lines_char( metrics, canvas, Lines { up: Style::Light, down: Style::Light, left: Style::Heavy, ..Lines::NONE }, ), 0x2526 => lines_char( metrics, canvas, Lines { up: Style::Heavy, left: Style::Light, down: Style::Light, ..Lines::NONE }, ), 0x2527 => lines_char( metrics, canvas, Lines { down: Style::Heavy, left: Style::Light, up: Style::Light, ..Lines::NONE }, ), 0x2528 => lines_char( metrics, canvas, Lines { up: Style::Heavy, down: Style::Heavy, left: Style::Light, ..Lines::NONE }, ), 0x2529 => lines_char( metrics, canvas, Lines { down: Style::Light, left: Style::Heavy, up: Style::Heavy, ..Lines::NONE }, ), 0x252a => lines_char( metrics, canvas, Lines { up: Style::Light, left: Style::Heavy, down: Style::Heavy, ..Lines::NONE }, ), 0x252b => lines_char( metrics, canvas, Lines { up: Style::Heavy, down: Style::Heavy, left: Style::Heavy, ..Lines::NONE }, ), 0x252c => lines_char( metrics, canvas, Lines { down: Style::Light, left: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x252d => lines_char( metrics, canvas, Lines { left: Style::Heavy, right: Style::Light, down: Style::Light, ..Lines::NONE }, ), 0x252e => lines_char( metrics, canvas, Lines { right: Style::Heavy, left: Style::Light, down: Style::Light, ..Lines::NONE }, ), 0x252f => lines_char( metrics, canvas, Lines { down: Style::Light, left: Style::Heavy, right: Style::Heavy, ..Lines::NONE }, ), 0x2530 => lines_char( metrics, canvas, Lines { down: Style::Heavy, left: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x2531 => lines_char( metrics, canvas, Lines { right: Style::Light, left: Style::Heavy, down: Style::Heavy, ..Lines::NONE }, ), 0x2532 => lines_char( metrics, canvas, Lines { left: Style::Light, right: Style::Heavy, down: Style::Heavy, ..Lines::NONE }, ), 0x2533 => lines_char( metrics, canvas, Lines { down: Style::Heavy, left: Style::Heavy, right: Style::Heavy, ..Lines::NONE }, ), 0x2534 => lines_char( metrics, canvas, Lines { up: Style::Light, left: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x2535 => lines_char( metrics, canvas, Lines { left: Style::Heavy, right: Style::Light, up: Style::Light, ..Lines::NONE }, ), 0x2536 => lines_char( metrics, canvas, Lines { right: Style::Heavy, left: Style::Light, up: Style::Light, ..Lines::NONE }, ), 0x2537 => lines_char( metrics, canvas, Lines { up: Style::Light, left: Style::Heavy, right: Style::Heavy, ..Lines::NONE }, ), 0x2538 => lines_char( metrics, canvas, Lines { up: Style::Heavy, left: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x2539 => lines_char( metrics, canvas, Lines { right: Style::Light, left: Style::Heavy, up: Style::Heavy, ..Lines::NONE }, ), 0x253a => lines_char( metrics, canvas, Lines { left: Style::Light, right: Style::Heavy, up: Style::Heavy, ..Lines::NONE }, ), 0x253b => lines_char( metrics, canvas, Lines { up: Style::Heavy, left: Style::Heavy, right: Style::Heavy, ..Lines::NONE }, ), 0x253c => lines_char( metrics, canvas, Lines { up: Style::Light, down: Style::Light, left: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x253d => lines_char( metrics, canvas, Lines { left: Style::Heavy, right: Style::Light, up: Style::Light, down: Style::Light, ..Lines::NONE }, ), 0x253e => lines_char( metrics, canvas, Lines { right: Style::Heavy, left: Style::Light, up: Style::Light, down: Style::Light, ..Lines::NONE }, ), 0x253f => lines_char( metrics, canvas, Lines { up: Style::Light, down: Style::Light, left: Style::Heavy, right: Style::Heavy, ..Lines::NONE }, ), 0x2540 => lines_char( metrics, canvas, Lines { up: Style::Heavy, down: Style::Light, left: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x2541 => lines_char( metrics, canvas, Lines { down: Style::Heavy, up: Style::Light, left: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x2542 => lines_char( metrics, canvas, Lines { up: Style::Heavy, down: Style::Heavy, left: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x2543 => lines_char( metrics, canvas, Lines { left: Style::Heavy, up: Style::Heavy, right: Style::Light, down: Style::Light, ..Lines::NONE }, ), 0x2544 => lines_char( metrics, canvas, Lines { right: Style::Heavy, up: Style::Heavy, left: Style::Light, down: Style::Light, ..Lines::NONE }, ), 0x2545 => lines_char( metrics, canvas, Lines { left: Style::Heavy, down: Style::Heavy, right: Style::Light, up: Style::Light, ..Lines::NONE }, ), 0x2546 => lines_char( metrics, canvas, Lines { right: Style::Heavy, down: Style::Heavy, left: Style::Light, up: Style::Light, ..Lines::NONE }, ), 0x2547 => lines_char( metrics, canvas, Lines { down: Style::Light, up: Style::Heavy, left: Style::Heavy, right: Style::Heavy, ..Lines::NONE }, ), 0x2548 => lines_char( metrics, canvas, Lines { up: Style::Light, down: Style::Heavy, left: Style::Heavy, right: Style::Heavy, ..Lines::NONE }, ), 0x2549 => lines_char( metrics, canvas, Lines { right: Style::Light, left: Style::Heavy, up: Style::Heavy, down: Style::Heavy, ..Lines::NONE }, ), 0x254a => lines_char( metrics, canvas, Lines { left: Style::Light, right: Style::Heavy, up: Style::Heavy, down: Style::Heavy, ..Lines::NONE }, ), 0x254b => lines_char( metrics, canvas, Lines { up: Style::Heavy, down: Style::Heavy, left: Style::Heavy, right: Style::Heavy, ..Lines::NONE }, ), 0x2550 => lines_char( metrics, canvas, Lines { left: Style::Double, right: Style::Double, ..Lines::NONE }, ), 0x2551 => lines_char( metrics, canvas, Lines { up: Style::Double, down: Style::Double, ..Lines::NONE }, ), 0x2552 => lines_char( metrics, canvas, Lines { down: Style::Light, right: Style::Double, ..Lines::NONE }, ), 0x2553 => lines_char( metrics, canvas, Lines { down: Style::Double, right: Style::Light, ..Lines::NONE }, ), 0x2554 => lines_char( metrics, canvas, Lines { down: Style::Double, right: Style::Double, ..Lines::NONE }, ), 0x2555 => lines_char( metrics, canvas, Lines { down: Style::Light, left: Style::Double, ..Lines::NONE }, ), 0x2556 => lines_char( metrics, canvas, Lines { down: Style::Double, left: Style::Light, ..Lines::NONE }, ), 0x2557 => lines_char( metrics, canvas, Lines { down: Style::Double, left: Style::Double, ..Lines::NONE }, ), 0x2558 => lines_char( metrics, canvas, Lines { up: Style::Light, right: Style::Double, ..Lines::NONE }, ), 0x2559 => lines_char( metrics, canvas, Lines { up: Style::Double, right: Style::Light, ..Lines::NONE }, ), 0x255a => lines_char( metrics, canvas, Lines { up: Style::Double, right: Style::Double, ..Lines::NONE }, ), 0x255b => lines_char( metrics, canvas, Lines { up: Style::Light, left: Style::Double, ..Lines::NONE }, ), 0x255c => lines_char( metrics, canvas, Lines { up: Style::Double, left: Style::Light, ..Lines::NONE }, ), 0x255d => lines_char( metrics, canvas, Lines { up: Style::Double, left: Style::Double, ..Lines::NONE }, ), 0x255e => lines_char( metrics, canvas, Lines { up: Style::Light, down: Style::Light, right: Style::Double, ..Lines::NONE }, ), 0x255f => lines_char( metrics, canvas, Lines { up: Style::Double, down: Style::Double, right: Style::Light, ..Lines::NONE }, ), 0x2560 => lines_char( metrics, canvas, Lines { up: Style::Double, down: Style::Double, right: Style::Double, ..Lines::NONE }, ), 0x2561 => lines_char( metrics, canvas, Lines { up: Style::Light, down: Style::Light, left: Style::Double, ..Lines::NONE }, ), 0x2562 => lines_char( metrics, canvas, Lines { up: Style::Double, down: Style::Double, left: Style::Light, ..Lines::NONE }, ), 0x2563 => lines_char( metrics, canvas, Lines { up: Style::Double, down: Style::Double, left: Style::Double, ..Lines::NONE }, ), 0x2564 => lines_char( metrics, canvas, Lines { down: Style::Light, left: Style::Double, right: Style::Double, ..Lines::NONE }, ), 0x2565 => lines_char( metrics, canvas, Lines { down: Style::Double, left: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x2566 => lines_char( metrics, canvas, Lines { down: Style::Double, left: Style::Double, right: Style::Double, ..Lines::NONE }, ), 0x2567 => lines_char( metrics, canvas, Lines { up: Style::Light, left: Style::Double, right: Style::Double, ..Lines::NONE }, ), 0x2568 => lines_char( metrics, canvas, Lines { up: Style::Double, left: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x2569 => lines_char( metrics, canvas, Lines { up: Style::Double, left: Style::Double, right: Style::Double, ..Lines::NONE }, ), 0x256a => lines_char( metrics, canvas, Lines { up: Style::Light, down: Style::Light, left: Style::Double, right: Style::Double, ..Lines::NONE }, ), 0x256b => lines_char( metrics, canvas, Lines { up: Style::Double, down: Style::Double, left: Style::Light, right: Style::Light, ..Lines::NONE }, ), 0x256c => lines_char( metrics, canvas, Lines { up: Style::Double, down: Style::Double, left: Style::Double, right: Style::Double, ..Lines::NONE }, ), 0x2574 => lines_char( metrics, canvas, Lines { left: Style::Light, ..Lines::NONE }, ), 0x2575 => lines_char( metrics, canvas, Lines { up: Style::Light, ..Lines::NONE }, ), 0x2576 => lines_char( metrics, canvas, Lines { right: Style::Light, ..Lines::NONE }, ), 0x2577 => lines_char( metrics, canvas, Lines { down: Style::Light, ..Lines::NONE }, ), 0x2578 => lines_char( metrics, canvas, Lines { left: Style::Heavy, ..Lines::NONE }, ), 0x2579 => lines_char( metrics, canvas, Lines { up: Style::Heavy, ..Lines::NONE }, ), 0x257a => lines_char( metrics, canvas, Lines { right: Style::Heavy, ..Lines::NONE }, ), 0x257b => lines_char( metrics, canvas, Lines { down: Style::Heavy, ..Lines::NONE }, ), 0x257c => lines_char( metrics, canvas, Lines { left: Style::Light, right: Style::Heavy, ..Lines::NONE }, ), 0x257d => lines_char( metrics, canvas, Lines { up: Style::Light, down: Style::Heavy, ..Lines::NONE }, ), 0x257e => lines_char( metrics, canvas, Lines { left: Style::Heavy, right: Style::Light, ..Lines::NONE }, ), 0x257f => lines_char( metrics, canvas, Lines { up: Style::Heavy, down: Style::Light, ..Lines::NONE }, ), _ => unreachable!(), } Ok(()) } pub fn lines_char(metrics: &Metrics, canvas: &mut Canvas, lines: Lines) { use Style::{Double, Heavy, Light, None}; let light_px = Thickness::Light.height(metrics.box_thickness); let heavy_px = Thickness::Heavy.height(metrics.box_thickness); // Top of light horizontal strokes let h_light_top = metrics.cell_height.saturating_sub(light_px) / 2; // Bottom of light horizontal strokes let h_light_bottom = h_light_top.saturating_add(light_px); // Top of heavy horizontal strokes let h_heavy_top = metrics.cell_height.saturating_sub(heavy_px) / 2; // Bottom of heavy horizontal strokes let h_heavy_bottom = h_heavy_top.saturating_add(heavy_px); // Top of the top doubled horizontal stroke (bottom is `h_light_top`) let h_double_top = h_light_top.saturating_sub(light_px); // Bottom of the bottom doubled horizontal stroke (top is `h_light_bottom`) let h_double_bottom = h_light_bottom.saturating_add(light_px); // Left of light vertical strokes let v_light_left = metrics.cell_width.saturating_sub(light_px) / 2; // Right of light vertical strokes let v_light_right = v_light_left.saturating_add(light_px); // Left of heavy vertical strokes let v_heavy_left = metrics.cell_width.saturating_sub(heavy_px) / 2; // Right of heavy vertical strokes let v_heavy_right = v_heavy_left.saturating_add(heavy_px); // Left of the left doubled vertical stroke (right is `v_light_left`) let v_double_left = v_light_left.saturating_sub(light_px); // Right of the right doubled vertical stroke (left is `v_light_right`) let v_double_right = v_light_right.saturating_add(light_px); // The bottom of the up line let up_bottom = if lines.left == Heavy || lines.right == Heavy { h_heavy_bottom } else if lines.left != lines.right || lines.down == lines.up { if lines.left == Double || lines.right == Double { h_double_bottom } else { h_light_bottom } } else if lines.left == None && lines.right == None { h_light_bottom } else { h_light_top }; // The top of the down line let down_top = if lines.left == Heavy || lines.right == Heavy { h_heavy_top } else if lines.left != lines.right || lines.up == lines.down { if lines.left == Double || lines.right == Double { h_double_top } else { h_light_top } } else if lines.left == None && lines.right == None { h_light_top } else { h_light_bottom }; // The right of the left line let left_right = if lines.up == Heavy || lines.down == Heavy { v_heavy_right } else if lines.up != lines.down || lines.left == lines.right { if lines.up == Double || lines.down == Double { v_double_right } else { v_light_right } } else if lines.up == None && lines.down == None { v_light_right } else { v_light_left }; // The left of the right line let right_left = if lines.up == Heavy || lines.down == Heavy { v_heavy_left } else if lines.up != lines.down || lines.right == lines.left { if lines.up == Double || lines.down == Double { v_double_left } else { v_light_left } } else if lines.up == None && lines.down == None { v_light_left } else { v_light_right }; let ch = metrics.cell_height as i32; let cw = metrics.cell_width as i32; match lines.up { None => {} Light => canvas.box_( v_light_left as i32, 0, v_light_right as i32, up_bottom as i32, Color::ON, ), Heavy => canvas.box_( v_heavy_left as i32, 0, v_heavy_right as i32, up_bottom as i32, Color::ON, ), Double => { let left_bottom = if lines.left == Double { h_light_top } else { up_bottom }; let right_bottom = if lines.right == Double { h_light_top } else { up_bottom }; canvas.box_( v_double_left as i32, 0, v_light_left as i32, left_bottom as i32, Color::ON, ); canvas.box_( v_light_right as i32, 0, v_double_right as i32, right_bottom as i32, Color::ON, ); } } match lines.right { None => {} Light => canvas.box_( right_left as i32, h_light_top as i32, cw, h_light_bottom as i32, Color::ON, ), Heavy => canvas.box_( right_left as i32, h_heavy_top as i32, cw, h_heavy_bottom as i32, Color::ON, ), Double => { let top_left = if lines.up == Double { v_light_right } else { right_left }; let bottom_left = if lines.down == Double { v_light_right } else { right_left }; canvas.box_( top_left as i32, h_double_top as i32, cw, h_light_top as i32, Color::ON, ); canvas.box_( bottom_left as i32, h_light_bottom as i32, cw, h_double_bottom as i32, Color::ON, ); } } match lines.down { None => {} Light => canvas.box_( v_light_left as i32, down_top as i32, v_light_right as i32, ch, Color::ON, ), Heavy => canvas.box_( v_heavy_left as i32, down_top as i32, v_heavy_right as i32, ch, Color::ON, ), Double => { let left_top = if lines.left == Double { h_light_bottom } else { down_top }; let right_top = if lines.right == Double { h_light_bottom } else { down_top }; canvas.box_( v_double_left as i32, left_top as i32, v_light_left as i32, ch, Color::ON, ); canvas.box_( v_light_right as i32, right_top as i32, v_double_right as i32, ch, Color::ON, ); } } match lines.left { None => {} Light => canvas.box_( 0, h_light_top as i32, left_right as i32, h_light_bottom as i32, Color::ON, ), Heavy => canvas.box_( 0, h_heavy_top as i32, left_right as i32, h_heavy_bottom as i32, Color::ON, ), Double => { let top_right = if lines.up == Double { v_light_left } else { left_right }; let bottom_right = if lines.down == Double { v_light_left } else { left_right }; canvas.box_( 0, h_double_top as i32, top_right as i32, h_light_top as i32, Color::ON, ); canvas.box_( 0, h_light_bottom as i32, bottom_right as i32, h_double_bottom as i32, Color::ON, ); } } } pub fn light_diagonal_upper_right_to_lower_left(metrics: &Metrics, canvas: &mut Canvas) { let float_width = f64::from(metrics.cell_width); let float_height = f64::from(metrics.cell_height); // We overshoot the corners by a tiny bit, but we need to // maintain the correct slope, so we calculate that here. let slope_x = (float_width / float_height).min(1.0); let slope_y = (float_height / float_width).min(1.0); let _ = canvas.line( Line { p0: Point { x: float_width + 0.5 * slope_x, y: -0.5 * slope_y, }, p1: Point { x: -0.5 * slope_x, y: float_height + 0.5 * slope_y, }, }, f64::from(Thickness::Light.height(metrics.box_thickness)), Color::ON, ); } pub fn light_diagonal_upper_left_to_lower_right(metrics: &Metrics, canvas: &mut Canvas) { let float_width = f64::from(metrics.cell_width); let float_height = f64::from(metrics.cell_height); // We overshoot the corners by a tiny bit, but we need to // maintain the correct slope, so we calculate that here. let slope_x = (float_width / float_height).min(1.0); let slope_y = (float_height / float_width).min(1.0); let _ = canvas.line( Line { p0: Point { x: -0.5 * slope_x, y: -0.5 * slope_y, }, p1: Point { x: float_width + 0.5 * slope_x, y: float_height + 0.5 * slope_y, }, }, f64::from(Thickness::Light.height(metrics.box_thickness)), Color::ON, ); } pub fn light_diagonal_cross(metrics: &Metrics, canvas: &mut Canvas) { light_diagonal_upper_right_to_lower_left(metrics, canvas); light_diagonal_upper_left_to_lower_right(metrics, canvas); } pub fn arc( metrics: &Metrics, canvas: &mut Canvas, corner: Corner, thickness: Thickness, ) -> Result<(), DrawError> { let thick_px = thickness.height(metrics.box_thickness); let float_width = f64::from(metrics.cell_width); let float_height = f64::from(metrics.cell_height); let float_thick = f64::from(thick_px); let center_x = f64::from(metrics.cell_width.saturating_sub(thick_px) / 2) + float_thick / 2.0; let center_y = f64::from(metrics.cell_height.saturating_sub(thick_px) / 2) + float_thick / 2.0; let r = float_width.min(float_height) / 2.0; // Fraction away from the center to place the middle control points, let s = 0.25; let mut path = Path::new(); match corner { Corner::Tl => { path.move_to(center_x, 0.0); path.line_to(center_x, center_y - r); path.curve_to( center_x, center_y - s * r, center_x - s * r, center_y, center_x - r, center_y, ); path.line_to(0.0, center_y); } Corner::Tr => { path.move_to(center_x, 0.0); path.line_to(center_x, center_y - r); path.curve_to( center_x, center_y - s * r, center_x + s * r, center_y, center_x + r, center_y, ); path.line_to(float_width, center_y); } Corner::Bl => { path.move_to(center_x, float_height); path.line_to(center_x, center_y + r); path.curve_to( center_x, center_y + s * r, center_x - s * r, center_y, center_x - r, center_y, ); path.line_to(0.0, center_y); } Corner::Br => { path.move_to(center_x, float_height); path.line_to(center_x, center_y + r); path.curve_to( center_x, center_y + s * r, center_x + s * r, center_y, center_x + r, center_y, ); path.line_to(float_width, center_y); } } canvas.stroke_path( &path, StrokeOptions { line_cap_mode: CapMode::Butt, line_width: float_thick, }, Color::ON, ) } fn dash_horizontal( metrics: &Metrics, canvas: &mut Canvas, count: u8, thick_px: u32, desired_gap: u32, ) { assert!((2..=4).contains(&count)); let count_u = u32::from(count); // Our dashed line should be made such that when tiled horizontally // it creates one consistent line with no uneven gap or segment sizes. // In order to make sure this is the case, we should have half-sized // gaps on the left and right so that it is centered properly. // For N dashes, there are N - 1 gaps between them, but we also have // half-sized gaps on either side, adding up to N total gaps. let gap_count = count_u; // We need at least 1 pixel for each gap and each dash, if we don't // have that then we can't draw our dashed line correctly so we just // draw a solid line and return. if metrics.cell_width < count_u + gap_count { hline_middle(metrics, canvas, Thickness::Light); return; } // We never want the gaps to take up more than 50% of the space, // because if they do the dashes are too small and look wrong. let gap_width = desired_gap.min(metrics.cell_width / (2 * count_u)) as i32; let total_gap_width = gap_count as i32 * gap_width; let total_dash_width = metrics.cell_width as i32 - total_gap_width; let dash_width = total_dash_width.div_euclid(i32::from(count)); let remaining = total_dash_width.rem_euclid(i32::from(count)); debug_assert_eq!( dash_width * i32::from(count) + gap_width * gap_count as i32 + remaining, metrics.cell_width as i32 ); // Our dashes should be centered vertically. let y = (metrics.cell_height.saturating_sub(thick_px) / 2) as i32; // We start at half a gap from the left edge, in order to center // our dashes properly. let mut x = gap_width.div_euclid(2); // We'll distribute the extra space in to dash widths, 1px at a // time. We prefer this to making gaps larger since that is much // more visually obvious. let mut extra = remaining; for _ in 0..count { let mut x1 = x + dash_width; // We distribute left-over size in to dash widths, // since it's less obvious there than in the gaps. if extra > 0 { extra -= 1; x1 += 1; } hline(canvas, x, x1, y, thick_px); // Advance by the width of the dash we drew and the width // of a gap to get the the start of the next dash. x = x1 + gap_width; } } fn dash_vertical( metrics: &Metrics, canvas: &mut Canvas, count: u8, thick_px: u32, desired_gap: u32, ) { assert!((2..=4).contains(&count)); let count_u = u32::from(count); // Our dashed line should be made such that when tiled vertically it // it creates one consistent line with no uneven gap or segment sizes. // In order to make sure this is the case, we should have an extra gap // gap at the bottom. // // A single full-sized extra gap is preferred to two half-sized ones for // vertical to allow better joining to solid characters without creating // visible half-sized gaps. Unlike horizontal, centering is a lot less // important, visually. // Because of the extra gap at the bottom, there are as many gaps as // there are dashes. let gap_count = count_u; // We need at least 1 pixel for each gap and each dash, if we don't // have that then we can't draw our dashed line correctly so we just // draw a solid line and return. if metrics.cell_height < count_u + gap_count { vline_middle(metrics, canvas, Thickness::Light); return; } // We never want the gaps to take up more than 50% of the space, // because if they do the dashes are too small and look wrong. let gap_height = desired_gap.min(metrics.cell_height / (2 * count_u)) as i32; let total_gap_height = gap_count as i32 * gap_height; let total_dash_height = metrics.cell_height as i32 - total_gap_height; let dash_height = total_dash_height.div_euclid(i32::from(count)); let remaining = total_dash_height.rem_euclid(i32::from(count)); debug_assert_eq!( dash_height * i32::from(count) + gap_height * gap_count as i32 + remaining, metrics.cell_height as i32 ); // Our dashes should be centered horizontally. let x = (metrics.cell_width.saturating_sub(thick_px) / 2) as i32; // We start at the top of the cell. let mut y = 0; // We'll distribute the extra space in to dash heights, 1px at a // time. We prefer this to making gaps larger since that is much // more visually obvious. let mut extra = remaining; for _ in 0..count { let mut y1 = y + dash_height; // We distribute left-over size in to dash widths, // since it's less obvious there than in the gaps. if extra > 0 { extra -= 1; y1 += 1; } vline(canvas, y, y1, x, thick_px); // Advance by the height of the dash we drew and the height // of a gap to get the the start of the next dash. y = y1 + gap_height; } } #[cfg(test)] mod tests { use super::*; fn metrics(w: u32, h: u32, t: u32) -> Metrics { Metrics::calc(crate::metrics::FaceMetrics { px_per_em: 16.0, cell_width: f64::from(w), ascent: f64::from(h) * 0.8, descent: -(f64::from(h) * 0.2), line_gap: 0.0, underline_thickness: Some(f64::from(t)), ..Default::default() }) } #[test] fn all_box_codepoints_draw() { for (w, h, t) in [(9, 17, 1), (12, 24, 3), (3, 5, 1)] { let m = metrics(w, h, t); for cp in 0x2500..=0x257f { let mut c = Canvas::new( m.cell_width, m.cell_height, m.cell_width / 4, m.cell_height / 4, ); draw2500_257f(cp, &mut c, m.cell_width, m.cell_height, &m).expect("draw"); assert!(c.sfc.iter().any(|v| *v != 0), "U+{cp:X} empty at {w}x{h}"); } } } #[test] fn tiny_cells_do_not_panic() { let m = metrics(1, 1, 1); for cp in 0x2500..=0x257f { let mut c = Canvas::new(1, 1, 0, 0); draw2500_257f(cp, &mut c, 1, 1, &m).expect("draw"); } } #[test] fn light_horizontal_line_is_centered() { let m = metrics(10, 20, 2); let mut c = Canvas::new(10, 20, 0, 0); lines_char( &m, &mut c, Lines { left: Style::Light, right: Style::Light, ..Lines::NONE }, ); let t = m.box_thickness as usize; let top = (20 - t) / 2; for y in 0..20usize { let on = c.sfc[y * 10] == 255; assert_eq!(on, y >= top && y < top + t, "row {y}"); } } }