Files
ghostty-rs/crates/ghostty-font/src/sprite/draw/box_.rs
T
ddidderr 8bcb85a29a style: apply nightly rustfmt import grouping across the workspace
rustfmt.toml sets group_imports, imports_granularity and imports_layout,
which are nightly-only; agents formatted with stable rustfmt, which
ignored them. This is a formatting-only pass with `cargo +nightly fmt`.

Test Plan:
- cargo clippy --workspace --all-targets -- -D warnings
- cargo test --workspace
2026-10-12 05:03:37 +02:00

1781 lines
48 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Box Drawing | U+2500...U+257F
//! <https://en.wikipedia.org/wiki/Box_Drawing>
//!
//! ─━│┃┄┅┆┇┈┉┊┋┌┍┎┏
//! ┐┑┒┓└┕┖┗┘┙┚┛├┝┞┟
//! ┠┡┢┣┤┥┦┧┨┩┪┫┬┭┮┯
//! ┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿
//! ╀╁╂╃╄╅╆╇╈╉╊╋╌╍╎╏
//! ═║╒╓╔╕╖╗╘╙╚╛╜╝╞╟
//! ╠╡╢╣╤╥╦╧╨╩╪╫╬╭╮╯
//! ╰╱╲╳╴╵╶╷╸╹╺╻╼╽╾╿
//!
//! 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}");
}
}
}