use crate::filter_effects::Filter;
#[cfg(feature = "text")]
use crate::glyph::{GlyphRenderer, GlyphRunBuilder, GlyphType, PreparedGlyph};
use crate::kurbo::{Affine, BezPath, Cap, Join, Rect, Stroke};
use crate::mask::Mask;
use crate::paint::{PaintType, Tint};
#[cfg(feature = "text")]
use crate::peniko::FontData;
use crate::peniko::color::palette::css::BLACK;
use crate::peniko::{BlendMode, Compose, Fill, Mix};
use crate::strip::Strip;
use crate::strip_generator::StripStorage;
use alloc::vec::Vec;
#[derive(Debug, Default)]
pub struct CachedStrips {
strip_storage: StripStorage,
strip_start_indices: Vec<usize>,
}
impl CachedStrips {
pub fn new(strip_storage: StripStorage, strip_start_indices: Vec<usize>) -> Self {
Self {
strip_storage,
strip_start_indices,
}
}
pub fn clear(&mut self) {
self.strip_storage.clear();
self.strip_start_indices.clear();
}
pub fn is_empty(&self) -> bool {
self.strip_storage.is_empty() && self.strip_start_indices.is_empty()
}
pub fn strip_count(&self) -> usize {
self.strip_storage.strips.len()
}
pub fn alpha_count(&self) -> usize {
self.strip_storage.alphas.len()
}
pub fn strips(&self) -> &[Strip] {
&self.strip_storage.strips
}
pub fn alphas(&self) -> &[u8] {
&self.strip_storage.alphas
}
pub fn strip_start_indices(&self) -> &[usize] {
&self.strip_start_indices
}
pub fn take(&mut self) -> (StripStorage, Vec<usize>) {
let strip_storage = core::mem::take(&mut self.strip_storage);
let strip_start_indices = core::mem::take(&mut self.strip_start_indices);
(strip_storage, strip_start_indices)
}
}
#[derive(Debug)]
pub struct Recording {
commands: Vec<RenderCommand>,
cached_strips: CachedStrips,
transform: Affine,
}
#[derive(Debug, Clone)]
pub struct PushLayerCommand {
pub clip_path: Option<BezPath>,
pub blend_mode: Option<BlendMode>,
pub opacity: Option<f32>,
pub mask: Option<Mask>,
pub filter: Option<Filter>,
}
#[derive(Debug)]
pub enum RenderCommand {
FillPath(BezPath),
StrokePath(BezPath),
FillRect(Rect),
StrokeRect(Rect),
SetTransform(Affine),
SetFillRule(Fill),
SetStroke(Stroke),
PushLayer(PushLayerCommand),
PopLayer,
SetPaint(PaintType),
SetPaintTransform(Affine),
ResetPaintTransform,
SetTint(Option<Tint>),
SetFilterEffect(Filter),
ResetFilterEffect,
#[cfg(feature = "text")]
FillOutlineGlyph((BezPath, Affine)),
#[cfg(feature = "text")]
StrokeOutlineGlyph((BezPath, Affine)),
}
impl Recording {
pub fn new() -> Self {
Self {
commands: Vec::new(),
cached_strips: CachedStrips::default(),
transform: Affine::IDENTITY,
}
}
pub(crate) fn set_transform(&mut self, transform: Affine) {
self.transform = transform;
}
pub fn commands(&self) -> &[RenderCommand] {
&self.commands
}
pub fn command_count(&self) -> usize {
self.commands.len()
}
pub fn has_cached_strips(&self) -> bool {
!self.cached_strips.is_empty()
}
pub fn strip_count(&self) -> usize {
self.cached_strips.strip_count()
}
pub fn alpha_count(&self) -> usize {
self.cached_strips.alpha_count()
}
pub fn get_cached_strips(&self) -> (&[Strip], &[u8]) {
(self.cached_strips.strips(), self.cached_strips.alphas())
}
pub fn take_cached_strips(&mut self) -> (StripStorage, Vec<usize>) {
self.cached_strips.take()
}
pub fn get_strip_start_indices(&self) -> &[usize] {
self.cached_strips.strip_start_indices()
}
pub fn clear(&mut self) {
self.commands.clear();
self.cached_strips.clear();
self.transform = Affine::IDENTITY;
}
pub(crate) fn add_command(&mut self, command: RenderCommand) {
self.commands.push(command);
}
pub fn set_cached_strips(
&mut self,
strip_storage: StripStorage,
strip_start_indices: Vec<usize>,
) {
self.cached_strips = CachedStrips::new(strip_storage, strip_start_indices);
}
}
impl Default for Recording {
fn default() -> Self {
Self::new()
}
}
pub trait Recordable {
fn record<F>(&mut self, recording: &mut Recording, f: F)
where
F: FnOnce(&mut Recorder<'_>);
fn prepare_recording(&mut self, recording: &mut Recording);
fn execute_recording(&mut self, recording: &Recording);
}
#[derive(Debug)]
pub struct Recorder<'a> {
recording: &'a mut Recording,
#[cfg(feature = "text")]
glyph_caches: Option<crate::glyph::GlyphCaches>,
}
impl<'a> Recorder<'a> {
pub fn new(
recording: &'a mut Recording,
transform: Affine,
#[cfg(feature = "text")] glyph_caches: crate::glyph::GlyphCaches,
) -> Self {
let mut s = Self {
recording,
#[cfg(feature = "text")]
glyph_caches: Some(glyph_caches),
};
s.set_transform(transform);
s
}
pub fn fill_path(&mut self, path: &BezPath) {
self.recording
.add_command(RenderCommand::FillPath(path.clone()));
}
pub fn stroke_path(&mut self, path: &BezPath) {
self.recording
.add_command(RenderCommand::StrokePath(path.clone()));
}
pub fn fill_rect(&mut self, rect: &Rect) {
self.recording.add_command(RenderCommand::FillRect(*rect));
}
pub fn stroke_rect(&mut self, rect: &Rect) {
self.recording.add_command(RenderCommand::StrokeRect(*rect));
}
pub fn set_transform(&mut self, transform: Affine) {
self.recording.set_transform(transform);
self.recording
.add_command(RenderCommand::SetTransform(transform));
}
pub fn set_fill_rule(&mut self, fill_rule: Fill) {
self.recording
.add_command(RenderCommand::SetFillRule(fill_rule));
}
pub fn set_stroke(&mut self, stroke: Stroke) {
self.recording.add_command(RenderCommand::SetStroke(stroke));
}
pub fn set_paint(&mut self, paint: impl Into<PaintType>) {
self.recording
.add_command(RenderCommand::SetPaint(paint.into()));
}
pub fn set_paint_transform(&mut self, paint_transform: Affine) {
self.recording
.add_command(RenderCommand::SetPaintTransform(paint_transform));
}
pub fn reset_paint_transform(&mut self) {
self.recording
.add_command(RenderCommand::ResetPaintTransform);
}
pub fn set_tint(&mut self, tint: Option<Tint>) {
self.recording.add_command(RenderCommand::SetTint(tint));
}
pub fn set_filter_effect(&mut self, filter: Filter) {
self.recording
.add_command(RenderCommand::SetFilterEffect(filter));
}
pub fn reset_filter_effect(&mut self) {
self.recording.add_command(RenderCommand::ResetFilterEffect);
}
pub fn push_layer(
&mut self,
clip_path: Option<&BezPath>,
blend_mode: Option<BlendMode>,
opacity: Option<f32>,
mask: Option<Mask>,
filter: Option<Filter>,
) {
self.recording
.add_command(RenderCommand::PushLayer(PushLayerCommand {
clip_path: clip_path.cloned(),
blend_mode,
opacity,
mask,
filter,
}));
}
pub fn push_clip_layer(&mut self, clip_path: &BezPath) {
self.push_layer(Some(clip_path), None, None, None, None);
}
pub fn push_filter_layer(&mut self, filter: Filter) {
self.push_layer(None, None, None, None, Some(filter));
}
pub fn pop_layer(&mut self) {
self.recording.add_command(RenderCommand::PopLayer);
}
#[cfg(feature = "text")]
pub fn glyph_run(&mut self, font: &FontData) -> GlyphRunBuilder<'_, Self> {
GlyphRunBuilder::new(font.clone(), self.recording.transform, self)
}
}
#[cfg(feature = "text")]
impl GlyphRenderer for Recorder<'_> {
fn fill_glyph(&mut self, glyph: PreparedGlyph<'_>) {
match glyph.glyph_type {
GlyphType::Outline(outline_glyph) => {
if !outline_glyph.path.is_empty() {
self.recording.add_command(RenderCommand::FillOutlineGlyph((
outline_glyph.path.clone(),
glyph.transform,
)));
}
}
_ => {
unimplemented!("Recording glyphs of type {:?}", glyph.glyph_type);
}
}
}
fn stroke_glyph(&mut self, glyph: PreparedGlyph<'_>) {
match glyph.glyph_type {
GlyphType::Outline(outline_glyph) => {
if !outline_glyph.path.is_empty() {
self.recording
.add_command(RenderCommand::StrokeOutlineGlyph((
outline_glyph.path.clone(),
glyph.transform,
)));
}
}
_ => {
unimplemented!("Recording glyphs of type {:?}", glyph.glyph_type);
}
}
}
fn restore_glyph_caches(&mut self, caches: crate::glyph::GlyphCaches) {
self.glyph_caches = Some(caches);
}
fn take_glyph_caches(&mut self) -> crate::glyph::GlyphCaches {
self.glyph_caches.take().unwrap_or_default()
}
}
#[derive(Debug, Clone)]
pub struct RenderState {
pub paint: PaintType,
pub paint_transform: Affine,
pub stroke: Stroke,
pub transform: Affine,
pub fill_rule: Fill,
pub blend_mode: BlendMode,
pub tint: Option<Tint>,
}
impl Default for RenderState {
fn default() -> Self {
Self {
paint: BLACK.into(),
paint_transform: Affine::IDENTITY,
stroke: Stroke {
width: 1.0,
join: Join::Bevel,
start_cap: Cap::Butt,
end_cap: Cap::Butt,
..Default::default()
},
transform: Affine::IDENTITY,
fill_rule: Fill::NonZero,
blend_mode: BlendMode::new(Mix::Normal, Compose::SrcOver),
tint: None,
}
}
}
impl RenderState {
pub fn reset(&mut self) {
*self = Self::default();
}
}