From 976c08fb390350a989de07cbdf4d15af90800ae7 Mon Sep 17 00:00:00 2001 From: Lucas Jansen <7199136+staticintlucas@users.noreply.github.com> Date: Fri, 4 Aug 2023 23:37:24 +0100 Subject: [PATCH] Draw text margin --- src/drawing/imp/key.rs | 186 +++++++++++++++++++++-------------------- src/drawing/imp/mod.rs | 71 +++++++++++++--- src/drawing/mod.rs | 4 +- src/drawing/svg.rs | 28 ++++--- 4 files changed, 171 insertions(+), 118 deletions(-) diff --git a/src/drawing/imp/key.rs b/src/drawing/imp/key.rs index 70bf1b8..b2d582c 100644 --- a/src/drawing/imp/key.rs +++ b/src/drawing/imp/key.rs @@ -3,116 +3,118 @@ use std::f64::consts::{FRAC_PI_2, PI}; use kurbo::{Arc, BezPath, Circle, Point, Rect, Shape, Size}; use crate::key::{Homing, Shape as KeyShape, Type as KeyType}; -use crate::utils::{Color, RoundRect}; +use crate::utils::RoundRect; use crate::{DrawingOptions, Key}; -use super::ARC_TOL; +use super::{Outline, Path, ARC_TOL}; -#[derive(Debug, Clone)] -pub(crate) struct KeyPath { - pub path: BezPath, - pub fill: Color, - pub outline: Color, -} - -impl KeyPath { - pub fn top(key: &Key, options: &DrawingOptions) -> Self { - let top_rect = options.profile.top_rect; +pub(crate) fn top(key: &Key, options: &DrawingOptions) -> Path { + let top_rect = options.profile.top_rect; - let path = match key.shape { - KeyShape::Normal(size) => top_rect - .with_size(top_rect.size() + 1e3 * (size - Size::new(1., 1.))) - .to_path(ARC_TOL), - KeyShape::SteppedCaps => top_rect - .with_size(top_rect.size() + 1e3 * (Size::new(0.25, 0.))) - .to_path(ARC_TOL), - KeyShape::IsoHorizontal | KeyShape::IsoVertical => iso_top_path(top_rect), - }; + let path = match key.shape { + KeyShape::Normal(size) => top_rect + .with_size(top_rect.size() + 1e3 * (size - Size::new(1., 1.))) + .to_path(ARC_TOL), + KeyShape::SteppedCaps => top_rect + .with_size(top_rect.size() + 1e3 * (Size::new(0.25, 0.))) + .to_path(ARC_TOL), + KeyShape::IsoHorizontal | KeyShape::IsoVertical => iso_top_path(top_rect), + }; - Self { - path, - fill: key.color, - outline: key.color.highlight(0.15), - } + Path { + path, + fill: Some(key.color), + outline: Some(Outline { + color: key.color.highlight(0.15), + width: options.outline_width, + }), } +} - pub fn bottom(key: &Key, options: &DrawingOptions) -> Self { - let bottom_rect = options.profile.bottom_rect; +pub(crate) fn bottom(key: &Key, options: &DrawingOptions) -> Path { + let bottom_rect = options.profile.bottom_rect; - let path = match key.shape { - KeyShape::Normal(size) => bottom_rect - .with_size(bottom_rect.size() + 1e3 * (size - Size::new(1., 1.))) - .to_path(ARC_TOL), - KeyShape::SteppedCaps => bottom_rect - .with_size(bottom_rect.size() + 1e3 * (Size::new(0.75, 0.))) - .to_path(ARC_TOL), - KeyShape::IsoHorizontal | KeyShape::IsoVertical => iso_bottom_path(bottom_rect), - }; + let path = match key.shape { + KeyShape::Normal(size) => bottom_rect + .with_size(bottom_rect.size() + 1e3 * (size - Size::new(1., 1.))) + .to_path(ARC_TOL), + KeyShape::SteppedCaps => bottom_rect + .with_size(bottom_rect.size() + 1e3 * (Size::new(0.75, 0.))) + .to_path(ARC_TOL), + KeyShape::IsoHorizontal | KeyShape::IsoVertical => iso_bottom_path(bottom_rect), + }; - Self { - path, - fill: key.color, - outline: key.color.highlight(0.15), - } + Path { + path, + fill: Some(key.color), + outline: Some(Outline { + color: key.color.highlight(0.15), + width: options.outline_width, + }), } +} - pub fn homing(key: &Key, options: &DrawingOptions) -> Option { - let profile = &options.profile; +pub(crate) fn homing(key: &Key, options: &DrawingOptions) -> Option { + let profile = &options.profile; - let KeyType::Homing(homing) = key.typ else { return None }; - let homing = homing.unwrap_or(profile.homing.default); + let KeyType::Homing(homing) = key.typ else { return None }; + let homing = homing.unwrap_or(profile.homing.default); - let center = profile - .top_rect - .rect() - .with_size(profile.top_rect.size() + 1e3 * (key.shape.size() - Size::new(1., 1.))) - .center(); + let center = profile + .top_rect + .rect() + .with_size(profile.top_rect.size() + 1e3 * (key.shape.size() - Size::new(1., 1.))) + .center(); - let bez_path = match homing { - Homing::Scoop => None, - Homing::Bar => Some( - Rect::from_center_size( - center + (0., profile.homing.bar.y_offset), - profile.homing.bar.size, - ) - .into_path(ARC_TOL), - ), - Homing::Bump => Some( - Circle::new( - center + (0., profile.homing.bump.y_offset), - profile.homing.bump.diameter / 2., - ) - .into_path(ARC_TOL), - ), - }; + let bez_path = match homing { + Homing::Scoop => None, + Homing::Bar => Some( + Rect::from_center_size( + center + (0., profile.homing.bar.y_offset), + profile.homing.bar.size, + ) + .into_path(ARC_TOL), + ), + Homing::Bump => Some( + Circle::new( + center + (0., profile.homing.bump.y_offset), + profile.homing.bump.diameter / 2., + ) + .into_path(ARC_TOL), + ), + }; - bez_path.map(|path| Self { - path, - fill: key.color, - outline: key.color.highlight(0.15), - }) - } + bez_path.map(|path| Path { + path, + fill: Some(key.color), + outline: Some(Outline { + color: key.color.highlight(0.15), + width: options.outline_width, + }), + }) +} - pub fn step(key: &Key, options: &DrawingOptions) -> Option { - matches!(key.shape, KeyShape::SteppedCaps).then(|| { - let profile = &options.profile; +pub(crate) fn step(key: &Key, options: &DrawingOptions) -> Option { + matches!(key.shape, KeyShape::SteppedCaps).then(|| { + let profile = &options.profile; - // Take average dimensions of top and bottom - let rect = RoundRect::from_origin_size( - ((profile.top_rect.origin().to_vec2() + profile.bottom_rect.origin().to_vec2()) - / 2.) - .to_point(), - (profile.top_rect.size() + profile.bottom_rect.size()) / 2., - (profile.top_rect.radii() + profile.bottom_rect.radii()) / 2., - ); + // Take average dimensions of top and bottom + let rect = RoundRect::from_origin_size( + ((profile.top_rect.origin().to_vec2() + profile.bottom_rect.origin().to_vec2()) / 2.) + .to_point(), + (profile.top_rect.size() + profile.bottom_rect.size()) / 2., + (profile.top_rect.radii() + profile.bottom_rect.radii()) / 2., + ); - Self { - path: step_path(rect), - fill: key.color, - outline: key.color.highlight(0.15), - } - }) - } + Path { + path: step_path(rect), + fill: Some(key.color), + outline: Some(Outline { + color: key.color.highlight(0.15), + width: options.outline_width, + }), + } + }) } fn iso_bottom_path(rect: RoundRect) -> BezPath { diff --git a/src/drawing/imp/mod.rs b/src/drawing/imp/mod.rs index eb75440..c3732c9 100644 --- a/src/drawing/imp/mod.rs +++ b/src/drawing/imp/mod.rs @@ -1,34 +1,85 @@ mod key; -use kurbo::Point; +use itertools::Itertools; +use kurbo::{BezPath, Point, Rect, Shape, Size, Vec2}; -use crate::key::Type as KeyType; +use crate::key::{Shape as KeyShape, Type as KeyType}; +use crate::utils::Color; use crate::{DrawingOptions, Key}; -pub(crate) use self::key::KeyPath; - // TODO move this somewhere? const ARC_TOL: f64 = 1.; // Tolerance for converting Arc->Bézier with Kurbo +#[derive(Debug, Clone, Copy)] +pub(crate) struct Outline { + pub color: Color, + pub width: f64, +} + +#[derive(Debug, Clone)] +pub(crate) struct Path { + pub path: BezPath, + pub outline: Option, + pub fill: Option, +} + #[derive(Debug, Clone)] pub(crate) struct KeyDrawing { pub origin: Point, - pub paths: Vec, + pub paths: Vec, } impl KeyDrawing { pub fn new(key: &Key, options: &DrawingOptions) -> Self { let show_key = options.show_keys && !matches!(key.typ, KeyType::None); - let bottom = show_key.then(|| KeyPath::bottom(key, options)); - let top = show_key.then(|| KeyPath::top(key, options)); - let step = show_key.then(|| KeyPath::step(key, options)).flatten(); - let homing = show_key.then(|| KeyPath::homing(key, options)).flatten(); + let bottom = show_key.then(|| key::bottom(key, options)); + let top = show_key.then(|| key::top(key, options)); + let step = show_key.then(|| key::step(key, options)).flatten(); + let homing = show_key.then(|| key::homing(key, options)).flatten(); + + let margin = options.show_margin.then(|| { + let (offset, size) = match key.shape { + KeyShape::Normal(size) => (Vec2::ZERO, 1e3 * (size - Size::new(1., 1.))), + KeyShape::SteppedCaps => (Vec2::ZERO, Size::new(250., 0.)), + KeyShape::IsoHorizontal => (Vec2::ZERO, Size::new(500., 0.)), + KeyShape::IsoVertical => (Vec2::new(250., 0.), Size::new(250., 1000.)), + }; + + let path = key + .legends + .iter() + .flatten() + .filter_map(|l| l.as_ref().map(|l| l.size)) + .unique() + .map(|s| options.profile.text_margin.get(s)) + .map(move |r| { + Rect::from_origin_size(r.origin() + offset, r.size() + size).into_path(ARC_TOL) + }) + .fold(BezPath::new(), |mut p, r| { + p.extend(r); + p + }); + + Path { + path, + outline: Some(Outline { + color: Color::new(0xff, 0, 0), + width: 5., + }), + fill: None, + } + }); // Do a bunch of chaining here rather than using [...].iter().filter_map(|it| it). This // gives iterator a known size so it will allocate the required size when collecting to a // Vec<_> - let paths = bottom.into_iter().chain(top).chain(step).chain(homing); + let paths = bottom + .into_iter() + .chain(top) + .chain(step) + .chain(homing) + .chain(margin); Self { origin: key.position, diff --git a/src/drawing/mod.rs b/src/drawing/mod.rs index 7ae817f..ae56f7f 100644 --- a/src/drawing/mod.rs +++ b/src/drawing/mod.rs @@ -5,14 +5,13 @@ use kurbo::{Point, Rect, Size}; use crate::{Font, Key, Profile}; -pub(crate) use imp::{KeyDrawing, KeyPath}; +pub(crate) use imp::{KeyDrawing, Path}; #[derive(Debug, Clone)] pub struct Drawing { bounds: Rect, keys: Vec, scale: f64, - outline: f64, } impl Drawing { @@ -34,7 +33,6 @@ impl Drawing { bounds, keys, scale: options.dpi * 0.75, // 1u = 0.75in => dpu = 0.75*dpi - outline: options.outline_width, } } diff --git a/src/drawing/svg.rs b/src/drawing/svg.rs index 890c10b..062ac06 100644 --- a/src/drawing/svg.rs +++ b/src/drawing/svg.rs @@ -1,11 +1,11 @@ use itertools::Itertools; use kurbo::{Affine, PathEl, Point}; -use svg::node::element::{Group, Path}; +use svg::node::element::{Group, Path as SvgPath}; use svg::Document; use crate::drawing::Drawing; -use super::{KeyDrawing, KeyPath}; +use super::{KeyDrawing, Path}; macro_rules! fmt_num { ($fmt:literal, $($args:expr),*) => { @@ -34,25 +34,22 @@ pub(crate) fn draw(drawing: &Drawing) -> String { let document = drawing .keys .iter() - .map(|k| draw_key(k, drawing.outline)) + .map(draw_key) .fold(document, Document::add); document.to_string() } -fn draw_key(key: &KeyDrawing, outline: f64) -> Group { +fn draw_key(key: &KeyDrawing) -> Group { // scale from keyboard units to drawing units (milliunits) let pos = Affine::scale(1e3) * key.origin; let group = Group::new().set("transform", fmt_num!("translate({}, {})", pos.x, pos.y)); - key.paths - .iter() - .map(|p| draw_path(p, outline)) - .fold(group, Group::add) + key.paths.iter().map(draw_path).fold(group, Group::add) } -fn draw_path(key: &KeyPath, outline: f64) -> Path { +fn draw_path(key: &Path) -> SvgPath { let data = key .path .iter() @@ -91,11 +88,16 @@ fn draw_path(key: &KeyPath, outline: f64) -> Path { }) .join(""); - let path = Path::new().set("d", data).set("fill", key.fill.to_string()); + let fill = if let Some(color) = key.fill { + color.to_string() + } else { + "none".into() + }; + let path = SvgPath::new().set("d", data).set("fill", fill); - if outline > 1e-3 { - path.set("stroke", key.outline.to_string()) - .set("stroke-width", fmt_num!("{}", outline)) + if let Some(outline) = key.outline { + path.set("stroke", outline.color.to_string()) + .set("stroke-width", fmt_num!("{}", outline.width)) } else { path.set("stroke", "none") }