hephaestus 0.1.0

Backend-agnostic 2D scene renderer for data visualization.
Documentation
//! Backend-agnostic scene authoring.
//!
//! [`SceneBuilder`] is the surface that plot code calls. Every method is
//! self-contained — no persistent "current transform / current brush" state —
//! which makes both immediate-mode and recording backends easy to implement.

use crate::blend::BlendMode;
use crate::brush::{Brush, Image, Sampling};
use crate::geometry::Affine;
use crate::mesh::Mesh;
use crate::path::{FillRule, Path};
use crate::pick::PickId;

pub mod recording;

/// A trait for issuing draw operations against some backend.
///
/// Implementations may rasterize immediately (e.g. Vello, Blend2D) or record
/// the calls for later replay (e.g. SVG, PDF).
pub trait SceneBuilder {
    /// Drop everything recorded so far, leaving the builder ready for a
    /// new frame. Part of the frame lifecycle, so generic code written
    /// against the trait can start one.
    fn clear(&mut self);

    /// Fill `path` with `brush`. `transform` applies to the path; `brush_transform`
    /// optionally transforms the brush coordinates (e.g. to rotate a gradient).
    ///
    /// `pick_id` controls how (or whether) this primitive appears in the
    /// hitmap when picking is enabled on the backend. Pass [`PickId::Skip`]
    /// for purely decorative content.
    fn fill(
        &mut self,
        rule: FillRule,
        transform: Affine,
        brush: &Brush,
        brush_transform: Option<Affine>,
        path: &Path,
        pick_id: PickId,
    );

    /// Stroke `path` with `brush`. See [`Self::fill`] for `pick_id` semantics.
    fn stroke(
        &mut self,
        stroke: &crate::stroke::Stroke,
        transform: Affine,
        brush: &Brush,
        brush_transform: Option<Affine>,
        path: &Path,
        pick_id: PickId,
    );

    /// Blit an image with the given transform. `alpha` is multiplied with the
    /// image's own alpha (0..=1). See [`Self::fill`] for `pick_id` semantics.
    fn draw_image(
        &mut self,
        image: &Image,
        transform: Affine,
        sampling: Sampling,
        alpha: f32,
        pick_id: PickId,
    );

    /// Draw a run of positioned glyphs. Shaping/layout is the caller's
    /// responsibility — this crate consumes already-placed glyphs.
    /// See [`Self::fill`] for `pick_id` semantics.
    fn draw_glyphs(&mut self, run: &GlyphRun<'_>, pick_id: PickId);

    /// Draw a 2D triangle mesh with per-vertex colour. `transform`
    /// applies to vertex positions (not colours). The whole mesh
    /// shares a single `pick_id` — picking does not distinguish
    /// individual triangles.
    ///
    /// No backend currently has a native indexed-mesh primitive;
    /// every backend decomposes the mesh into its own draw ops (e.g.
    /// the Vello backend emits one `fill` per triangle with a
    /// per-triangle linear-gradient brush). See [`Self::fill`] for
    /// `pick_id` semantics.
    fn draw_mesh(&mut self, mesh: &Mesh, transform: Affine, pick_id: PickId);

    /// Push a layer. Subsequent draws are clipped to `clip` (transformed by
    /// `transform`) and composited into the parent layer using `blend` and
    /// `alpha`. Must be matched by [`Self::pop_layer`].
    fn push_layer(&mut self, blend: BlendMode, alpha: f32, transform: Affine, clip: &Path);

    /// Pop the most recently pushed layer.
    fn pop_layer(&mut self);
}

// ---------- glyph types ----------

/// Opaque font handle. Wraps `peniko::FontData` (an Arc-backed font blob + index).
#[derive(Debug, Clone)]
pub struct Font(peniko::FontData);

impl Font {
    /// Wrap an already-resolved backend font. Crate-internal: the
    /// public way in is [`Self::new`].
    pub(crate) fn from_data(data: peniko::FontData) -> Self {
        Self(data)
    }

    /// Borrow the backend font blob. Crate-internal so the handle
    /// stays opaque on the public surface.
    #[cfg_attr(not(feature = "vello"), allow(dead_code))]
    pub(crate) fn data(&self) -> &peniko::FontData {
        &self.0
    }

    /// Wrap a font blob and face index. `data` is an Arc-backed byte
    /// buffer carrying the font file; `index` selects a face within a
    /// TrueType / OpenType collection (0 for the common single-face case).
    pub fn new(data: peniko::Blob<u8>, index: u32) -> Self {
        Self(peniko::FontData::new(data, index))
    }
}

/// A single positioned glyph.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Glyph {
    pub id: u32,
    pub x: f32,
    pub y: f32,
}

/// A run of glyphs sharing the same font, size, transform, and brush.
#[derive(Debug, Clone, Copy)]
pub struct GlyphRun<'a> {
    pub font: &'a Font,
    pub font_size: f32,
    pub transform: Affine,
    /// Optional per-glyph transform (skew, etc.) applied in glyph space.
    pub glyph_transform: Option<Affine>,
    pub brush: &'a Brush,
    /// Brush alpha multiplier (0..=1).
    pub brush_alpha: f32,
    /// If true, the backend may apply hinting where supported.
    pub hint: bool,
    pub glyphs: &'a [Glyph],
    /// Render style. `None` (default) fills the glyph outlines; `Some(stroke)`
    /// strokes them along the outline with the given pen. Used for outlined
    /// text — typically paired with a separate filled pass on top.
    pub style: Option<&'a crate::stroke::Stroke>,
}