mirui 0.44.0

A lightweight, no_std ECS-driven UI framework for embedded, desktop, and WebAssembly
Documentation
use super::SdlGpuRenderer;
use crate::render::path::Path;
use crate::render::raster::{FillRule, LineCap, LineJoin, StrokeSpec};
use crate::types::{Color, Fixed, Rect, Transform};

impl<S: AsRef<[u8]> + AsMut<[u8]>> SdlGpuRenderer<'_, S> {
    pub(super) fn fill_path_inner(
        &mut self,
        path: &Path,
        clip: &Rect,
        color: &Color,
        opa: u8,
        fill_rule: FillRule,
    ) {
        let phys_tf = self.viewport.as_transform();
        let phys_clip = self.viewport.rect_to_physical(*clip);
        self.tessellator
            .fill(path, Some(&phys_tf), color, opa, fill_rule);
        self.submit_geometry(&phys_clip, opa != 255 || color.a != 255);
    }

    pub(super) fn fill_path_transformed_inner(
        &mut self,
        path: &Path,
        clip: &Rect,
        cmd_tf: &Transform,
        color: &Color,
        opa: u8,
        fill_rule: FillRule,
    ) {
        let phys_tf = self.viewport.as_transform().compose(cmd_tf);
        let phys_clip = self.viewport.rect_to_physical(*clip);
        self.tessellator
            .fill(path, Some(&phys_tf), color, opa, fill_rule);
        self.submit_geometry(&phys_clip, opa != 255 || color.a != 255);
    }

    pub(super) fn stroke_path_inner(
        &mut self,
        path: &Path,
        clip: &Rect,
        width: Fixed,
        color: &Color,
        opa: u8,
    ) {
        self.stroke_path_styled_inner(
            path,
            clip,
            &Transform::IDENTITY,
            StrokeSpec {
                width,
                cap: LineCap::Butt,
                join: LineJoin::Miter,
                miter_limit: Fixed::from_int(4),
                dash: &[],
                dash_scale: Fixed::ONE,
            },
            color,
            opa,
        );
    }

    pub(super) fn stroke_path_styled_inner(
        &mut self,
        path: &Path,
        clip: &Rect,
        cmd_tf: &Transform,
        spec: StrokeSpec<'_>,
        color: &Color,
        opa: u8,
    ) {
        if spec.width <= Fixed::ZERO || opa == 0 {
            return;
        }
        let scale = self.viewport.scale();
        let phys_tf = self.viewport.as_transform().compose(cmd_tf);
        if spec.dash.is_empty() {
            let mut physical = StrokeSpec {
                width: spec.width * scale,
                dash_scale: scale,
                ..spec
            };
            let mut physical_opacity = opa;
            if physical.width <= Fixed::ONE {
                let requested = physical.width;
                physical.width += Fixed::HALF;
                physical_opacity = coverage_opacity(opa, requested, physical.width);
            }
            self.tessellator
                .stroke(path, Some(&phys_tf), physical, color, physical_opacity);
            let phys_clip = self.viewport.rect_to_physical(*clip);
            self.submit_geometry(&phys_clip, physical_opacity != 255 || color.a != 255);
            return;
        }
        let outline = self.stroke_scratch.outline(
            path,
            Some(&phys_tf),
            StrokeSpec {
                width: spec.width * scale,
                dash_scale: scale,
                ..spec
            },
        );
        self.tessellator
            .fill(outline, None, color, opa, FillRule::NonZero);
        let phys_clip = self.viewport.rect_to_physical(*clip);
        self.submit_geometry(&phys_clip, opa != 255 || color.a != 255);
    }
}

fn coverage_opacity(opacity: u8, requested_width: Fixed, raster_width: Fixed) -> u8 {
    if raster_width <= Fixed::ZERO {
        return 0;
    }
    (Fixed::from_int(i32::from(opacity)) * requested_width / raster_width)
        .round()
        .clamp(Fixed::ZERO, Fixed::from_int(255))
        .to_int() as u8
}

#[cfg(test)]
mod tests {
    use super::coverage_opacity;
    use crate::types::Fixed;

    #[test]
    fn one_pixel_stroke_preserves_integrated_coverage_when_expanded() {
        assert_eq!(
            coverage_opacity(150, Fixed::ONE, Fixed::from_ratio(3, 2)),
            100
        );
    }
}