Skip to content

Commit

Permalink
Improve drawing traits, adjust parsing
Browse files Browse the repository at this point in the history
- Restructure `Canvas` and `Draw`
- Enable parsing of Altium arrays
- Restructure logging
  • Loading branch information
tgross35 committed Aug 24, 2023
1 parent 70699de commit c292606
Show file tree
Hide file tree
Showing 13 changed files with 657 additions and 273 deletions.
51 changes: 40 additions & 11 deletions altium/src/common.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::{fmt, str};

use uuid::Uuid;

use crate::error::{ErrorKind, TruncBuf};
use crate::parse::{FromUtf8, ParseUtf8};

Expand All @@ -15,17 +17,20 @@ pub struct Location {
}

impl Location {
#[must_use]
pub fn new(x: i32, y: i32) -> Self {
Self { x, y }
}

#[must_use]
pub fn add_x(self, x: i32) -> Self {
Self {
x: self.x + x,
y: self.y,
}
}

#[must_use]
pub fn add_y(self, y: i32) -> Self {
Self {
x: self.x,
Expand All @@ -45,36 +50,53 @@ pub enum Visibility {
///
// TODO: figure out what file types use this exact format
#[derive(Clone, Copy, PartialEq)]
pub struct UniqueId([u8; 8]);
pub enum UniqueId {
/// Altium's old style UUID
Simple([u8; 8]),
Uuid(Uuid),
}

impl UniqueId {
pub(crate) fn from_slice<S: AsRef<[u8]>>(buf: S) -> Option<Self> {
buf.as_ref().try_into().ok().map(Self)
fn from_slice<S: AsRef<[u8]>>(buf: S) -> Option<Self> {
buf.as_ref()
.try_into()
.ok()
.map(Self::Simple)
.or_else(|| Uuid::try_parse_ascii(buf.as_ref()).ok().map(Self::Uuid))
}
}

/// Get this `UniqueId` as a string
pub fn as_str(&self) -> &str {
str::from_utf8(&self.0).expect("unique IDs should always be ASCII")
impl fmt::Display for UniqueId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UniqueId::Simple(v) => str::from_utf8(v)
.expect("unique IDs should always be ASCII")
.fmt(f),
UniqueId::Uuid(v) => v.as_hyphenated().fmt(f),
}
}
}

impl Default for UniqueId {
fn default() -> Self {
Self(*b"00000000")
Self::Simple(*b"00000000")
}
}

impl fmt::Debug for UniqueId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("UniqueId").field(&self.as_str()).finish()
f.debug_tuple("UniqueId").field(&self.to_string()).finish()
}
}

impl FromUtf8<'_> for UniqueId {
fn from_utf8(buf: &[u8]) -> Result<Self, ErrorKind> {
Ok(Self(buf.as_ref().try_into().map_err(|_| {
ErrorKind::InvalidUniqueId(TruncBuf::new(buf))
})?))
buf.as_ref()
.try_into()
.ok()
.map(Self::Simple)
.or_else(|| Uuid::try_parse_ascii(buf).ok().map(Self::Uuid))
.ok_or(ErrorKind::InvalidUniqueId(TruncBuf::new(buf)))
}
}

Expand Down Expand Up @@ -225,3 +247,10 @@ pub enum PosVert {
Middle,
Bottom,
}

/// Verify a number pattern matches, e.g. `X100`
pub fn is_number_pattern(s: &[u8], prefix: u8) -> bool {
s.strip_prefix(&[prefix])
.map(|s| s.strip_prefix(&[b'-']).unwrap_or(s))
.is_some_and(|s| s.iter().all(u8::is_ascii_digit))
}
58 changes: 58 additions & 0 deletions altium/src/draw/canvas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use crate::{
common::{Color, Location, PosHoriz, PosVert, Rotation},
font::Font,
sch::Justification,
};

/// Generic trait for something that can be drawn. Beware, unstable!
pub trait Canvas {
fn draw_text(&mut self, item: DrawText);
fn draw_line(&mut self, item: DrawLine);
fn draw_polygon(&mut self, item: DrawPolygon);
fn draw_rectangle(&mut self, item: DrawRectangle);
fn draw_image(&mut self, item: DrawImage);
fn add_comment<S: Into<String>>(&mut self, comment: S) {}
}

/// Helper struct to write some text
#[derive(Clone, Debug, Default)]
pub struct DrawText<'a> {
pub x: i32,
pub y: i32,
pub text: &'a str,
pub font: &'a Font,
pub anchor_h: PosHoriz,
pub anchor_v: PosVert,
pub color: Color,
pub rotation: Rotation,
}

#[derive(Clone, Debug, Default)]
pub struct DrawLine {
pub start: Location,
pub end: Location,
pub color: Color,
pub width: u16,
// pub width: Option<&'a str>,
}

#[derive(Clone, Debug, Default)]
pub struct DrawRectangle {
pub x: i32,
pub y: i32,
pub width: i32,
pub height: i32,
pub fill_color: Color,
pub stroke_color: Color,
pub stroke_width: u16,
}

#[derive(Clone, Debug, Default)]
pub struct DrawPolygon<'a> {
pub locations: &'a [Location],
pub fill_color: Color,
pub stroke_color: Color,
pub stroke_width: u16,
}

pub struct DrawImage {}
115 changes: 6 additions & 109 deletions altium/src/draw/mod.rs
Original file line number Diff line number Diff line change
@@ -1,107 +1,17 @@
mod text;
pub(crate) mod canvas;
mod svg;

use core::{
cmp::{max, min},
mem,
};

use svg::node::element::SVG as Svg;
pub use text::{DrawLine, DrawText};
pub use canvas::{Canvas, DrawImage, DrawLine, DrawPolygon, DrawRectangle, DrawText};
pub use svg::SvgCtx;

pub use crate::common::{Color, Location, PosHoriz, PosVert};
use crate::font::Font;

pub struct SvgCtx {
svg: Svg,
/// `(min, max)` values of x
x_range: Option<(i32, i32)>,
/// `(min, max)` values of y
y_range: Option<(i32, i32)>,
/// True if the image header has already been set
has_embedded_images: bool,
}

impl SvgCtx {
pub fn new() -> Self {
Self {
svg: Svg::new(),
x_range: None,
y_range: None,
has_embedded_images: false,
}
}

/// Add a node to this svg
pub fn add_node<T>(&mut self, node: T)
where
T: Into<Box<dyn svg::Node>>,
{
// Bad API means we need to do memory tricks...
let mut working = Svg::new();
mem::swap(&mut self.svg, &mut working);
working = working.add(node);
mem::swap(&mut self.svg, &mut working);
}

/// Translate from (0, 0) in bottom left to (0, 0) in top left. Makes sure
/// `x` and `x + width` are within the view box.
pub fn x_coord(&mut self, x: i32, width: i32) -> i32 {
let (mut min_x, mut max_x) = self.x_range.unwrap_or((x, x));
let edge = x + width; // Add width (allows for negative values)
min_x = min(min(min_x, x), edge);
max_x = max(max(max_x, x), edge);

self.x_range = Some((min_x, max_x));
x
}

/// Translate from (0, 0) in bottom left to (0, 0) in top left
///
/// Updates the y location ranges if needed
pub fn y_coord(&mut self, y: i32, height: i32) -> i32 {
let new_y = -y - height;
let (mut min_y, mut max_y) = self.y_range.unwrap_or((new_y, new_y));
let edge = new_y + height; // Add height (allows for negative values)
min_y = min(min(min_y, new_y), edge);
max_y = max(max(max_y, new_y), edge);

self.y_range = Some((min_y, max_y));
new_y
}

/// Get the svg
pub fn svg(self) -> Svg {
let mut svg = self.svg;
let (min_x, max_x) = self.x_range.unwrap_or((0, 0));
let (min_y, max_y) = self.y_range.unwrap_or((0, 0));

// Add a 5% border on all sides
let side_extra = (max_x - min_x) / 20;
let vert_extra = (max_y - min_y) / 20;

svg = svg.set(
"viewBox",
format!(
"{} {} {} {}",
min_x - side_extra,
min_y - vert_extra,
(max_x - min_x) + side_extra * 2,
(max_y - min_y) + vert_extra * 2,
),
);

if self.has_embedded_images {
svg = svg.set("xmlns:xlink", "http://www.w3.org/1999/xlink");
}

svg
}

/// Set xlink header for embedded images
pub fn enable_inline_images(&mut self) {
self.has_embedded_images = true;
}
}

pub trait Draw {
type Context<'a>;

Expand All @@ -110,18 +20,5 @@ pub trait Draw {
/// This has a defualt implementation that does nothing for easier
/// reusability
#[allow(unused)]
fn draw_svg(&self, svg: &mut SvgCtx, ctx: &Self::Context<'_>) {}
}

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_coord_offsets() {
let mut svg = SvgCtx::new();
assert_eq!(10, svg.x_coord(10, 20));
assert_eq!(-30, svg.y_coord(10, 20));
assert_eq!(svg.x_range, Some((10, 30)));
assert_eq!(svg.y_range, Some((-30, -10)));
}
fn draw<C: Canvas>(&self, canvas: &mut C, ctx: &Self::Context<'_>) {}
}
Loading

0 comments on commit c292606

Please sign in to comment.