feat(font): port font metrics, atlas and the sprite font
Ports ZIG/src/font/{Metrics,Atlas,Glyph}.zig and the whole sprite font
(font/sprite/**): box drawing, block elements, braille, branch/git
graph symbols, geometric shapes, powerline, and the symbols for legacy
computing (+ supplement, incl. the octants table).
Ghostty draws sprites with z2d; we use tiny-skia for path coverage,
composited onto an alpha8 canvas, while following the Zig geometry
(positions, thickness, rounding). Comptime discovery of draw functions
is replaced by explicit, overlap-checked codepoint range tables.
Upstream reference sheets are vendored in testdata/ and compared with a
tolerance: braille and U+1CD00 are pixel-identical; other sheets differ
only in antialiasing on thin diagonals/curves. Review renders are written
to target/sprite_sheets/.
The crate temporarily drops its config/unicode/terminal deps until the
rest of the font engine is ported (Phase 3).
Test Plan:
- cargo test -p ghostty-font (49 tests incl. reference sheet comparison)
- inspect target/sprite_sheets/review-all-18x36.png
This commit is contained in:
1 parent
b2b248e003
commit
1388f117d3
58 files changed
+9331
-6
No files matched your search
@@ -0,0 +1,1778 @@
|
||||
//! 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;
|
||||
use crate::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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user