standard-plugin-sdk 0.1.1

Write Standard Code plugins in Rust: wasm components against standard:plugin@2.0.0
Documentation
//! Vector shapes drawn into a plugin's own surface buffer, in either model.
//!
//! [`Shapes`] is implemented by [`Surface<Cells>`](crate::Surface) (ink: a
//! [`Style`](crate::Style); box-drawing and block characters) and by
//! [`Surface<Pixels>`](crate::Surface) (ink: an [`Rgba`](crate::Rgba);
//! antialiased edges). Coordinates are cells or pixels and may lie outside
//! the surface: everything is clipped, and only what is drawn is marked
//! dirty.
//!
//! ```rust,ignore
#![doc = include_str!("../../examples/shapes_stage.rs")]
//! ```

mod cells;
pub mod font;
mod pixels;

pub use font::{GLYPH_ADVANCE, GLYPH_HEIGHT, GLYPH_WIDTH};
pub use pixels::Image;

/// A rectangle whose origin may lie outside the surface.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct Area {
    pub x: i32,
    pub y: i32,
    pub w: u32,
    pub h: u32,
}

impl Area {
    pub const fn new(x: i32, y: i32, w: u32, h: u32) -> Self {
        Self { x, y, w, h }
    }

    pub(crate) fn left(&self) -> i64 {
        i64::from(self.x)
    }

    pub(crate) fn top(&self) -> i64 {
        i64::from(self.y)
    }

    pub(crate) fn right(&self) -> i64 {
        i64::from(self.x) + i64::from(self.w)
    }

    pub(crate) fn bottom(&self) -> i64 {
        i64::from(self.y) + i64::from(self.h)
    }
}

impl From<crate::Rect> for Area {
    fn from(rect: crate::Rect) -> Self {
        Self {
            x: rect.x.min(i32::MAX as u32) as i32,
            y: rect.y.min(i32::MAX as u32) as i32,
            w: rect.w,
            h: rect.h,
        }
    }
}

/// Shapes both surface models draw. `Ink` is what paints: a style for
/// cells, a colour for pixels.
pub trait Shapes {
    type Ink: Copy;

    /// A one-unit outline of `area`.
    fn stroke_rect(&mut self, area: Area, ink: Self::Ink);

    /// `area`, filled.
    fn fill_rect(&mut self, area: Area, ink: Self::Ink);

    /// A line from `from` to `to`, both ends included.
    fn line(&mut self, from: (i32, i32), to: (i32, i32), ink: Self::Ink);

    /// A horizontal bar across `area`: the left `fraction` (0 to 1) in
    /// `fill`, the rest in `track`. Partial units are shown with eighth
    /// blocks (cells) or coverage (pixels).
    fn bar(&mut self, area: Area, fraction: f32, fill: Self::Ink, track: Self::Ink);

    /// The outline of `area` with corners of `radius` (cells round their
    /// corners with arcs whatever the radius; a zero radius is square).
    fn rounded_rect(&mut self, area: Area, radius: u32, ink: Self::Ink);

    /// `area` filled, with corners of `radius`.
    fn fill_rounded_rect(&mut self, area: Area, radius: u32, ink: Self::Ink);

    /// A circle's outline around `centre`, in continuous coordinates: the
    /// unit at `(x, y)` spans `x..x + 1`, so `(x + 0.5, y + 0.5)` is its
    /// centre, and a moving shape can sit between units (pixels antialias
    /// it; cells round to the nearest cell). Cells are about twice as tall
    /// as wide, so in cells the radius counts rows and spans twice as many
    /// columns.
    fn circle(&mut self, centre: (f32, f32), radius: f32, ink: Self::Ink);

    /// A filled circle (see [`Shapes::circle`]).
    fn fill_circle(&mut self, centre: (f32, f32), radius: f32, ink: Self::Ink);

    /// Text from `(x, y)`, clipped: cells write graphemes; pixels draw the
    /// built-in 5x7 font ([`GLYPH_ADVANCE`] pixels per character, ASCII;
    /// other characters show as a box). Returns the units advanced.
    fn label(&mut self, x: i32, y: i32, text: &str, ink: Self::Ink) -> u32;
}

/// `fraction` clamped to 0..=1 (NaN is 0).
pub(crate) fn unit(fraction: f32) -> f32 {
    if fraction.is_nan() {
        0.0
    } else {
        fraction.clamp(0.0, 1.0)
    }
}

/// The part of the segment inside `[-1, width] x [-1, height]` (a margin
/// of one unit so antialiased edges stay whole), or `None` when it misses.
/// Liang-Barsky; the result is rounded to whole units.
pub(crate) fn clip_segment(
    from: (i64, i64),
    to: (i64, i64),
    width: u32,
    height: u32,
) -> Option<((i64, i64), (i64, i64))> {
    let (x0, y0) = (from.0 as f64, from.1 as f64);
    let (dx, dy) = ((to.0 - from.0) as f64, (to.1 - from.1) as f64);
    let (min_x, max_x) = (-1.0, f64::from(width));
    let (min_y, max_y) = (-1.0, f64::from(height));
    let (mut enter, mut leave) = (0.0f64, 1.0f64);
    for (p, q) in [
        (-dx, x0 - min_x),
        (dx, max_x - x0),
        (-dy, y0 - min_y),
        (dy, max_y - y0),
    ] {
        if p == 0.0 {
            if q < 0.0 {
                return None;
            }
            continue;
        }
        let t = q / p;
        if p < 0.0 {
            enter = enter.max(t);
        } else {
            leave = leave.min(t);
        }
        if enter > leave {
            return None;
        }
    }
    let point = |t: f64| {
        (
            libm::round(x0 + t * dx) as i64,
            libm::round(y0 + t * dy) as i64,
        )
    };
    Some((point(enter), point(leave)))
}