Remove most of document-legacy (#1519)

* Remove boolean ops and unused doc-legacy Operations

* Remove Shape legacy layers

* Remove legacy layer Properties panel code

* Remove additional unused doc-legacy Operations

* Removed unused rendering-related legacy-layer code

* Upgrade dep so CI builds

* Remove various additional unused functions and messages

* Remove the LayerData trait

* Remove RenderData struct and usages

* Banish the Operations system

* Further removals
This commit is contained in:
Keavon Chambers
2023-12-19 04:36:19 -08:00
parent c42d030f18
commit 9a7d7de8fa
64 changed files with 330 additions and 5868 deletions
@@ -1,23 +0,0 @@
//! Basic wrapper for [`serde`] for [`base64`] encoding
use base64::Engine;
use serde::{Deserialize, Deserializer, Serializer};
pub fn as_base64<S>(key: &std::sync::Arc<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&base64::engine::general_purpose::STANDARD.encode(key.as_slice()))
}
pub fn from_base64<'a, D>(deserializer: D) -> Result<std::sync::Arc<Vec<u8>>, D::Error>
where
D: Deserializer<'a>,
{
use serde::de::Error;
String::deserialize(deserializer)
.and_then(|string| base64::engine::general_purpose::STANDARD.decode(string).map_err(|err| Error::custom(err.to_string())))
.map(std::sync::Arc::new)
.map_err(serde::de::Error::custom)
}
+9 -224
View File
@@ -1,242 +1,27 @@
use super::layer_info::{LayerData, LegacyLayer, LegacyLayerType};
use super::style::RenderData;
use crate::intersection::Quad;
use crate::{DocumentError, LayerId};
use super::layer_info::LegacyLayer;
use crate::document::LayerId;
use crate::DocumentError;
use graphene_core::uuid::generate_uuid;
use glam::DVec2;
use serde::{Deserialize, Serialize};
/// A layer that encapsulates other layers, including potentially more folders.
/// The contained layers are rendered in the same order they are stored.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
pub struct FolderLegacyLayer {
/// The ID that will be assigned to the next layer that is added to the folder
next_assignment_id: LayerId,
/// The IDs of the [Layer]s contained within the Folder
pub layer_ids: Vec<LayerId>,
/// The [Layer]s contained in the folder
pub layers: Vec<LegacyLayer>,
}
impl LayerData for FolderLegacyLayer {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<glam::DAffine2>, render_data: &RenderData) -> bool {
let mut any_child_requires_redraw = false;
for layer in &mut self.layers {
let (svg_value, requires_redraw) = layer.render(transforms, svg_defs, render_data);
*svg += svg_value;
any_child_requires_redraw = any_child_requires_redraw || requires_redraw;
}
any_child_requires_redraw
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, render_data: &RenderData) {
for (layer, layer_id) in self.layers().iter().zip(&self.layer_ids) {
path.push(*layer_id);
layer.intersects_quad(quad, path, intersections, render_data);
path.pop();
}
}
fn bounding_box(&self, transform: glam::DAffine2, render_data: &RenderData) -> Option<[DVec2; 2]> {
self.layers
.iter()
.filter_map(|layer| layer.data.bounding_box(transform * layer.transform, render_data))
.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])])
}
}
impl FolderLegacyLayer {
/// When a insertion ID is provided, try to insert the layer with the given ID.
/// If that ID is already used, return `None`.
/// When no insertion ID is provided, search for the next free ID and insert it with that.
/// Negative values for `insert_index` represent distance from the end
///
/// # Example
/// ```
/// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer;
/// # use graphite_document_legacy::layers::folder_layer::FolderLegacyLayer;
/// # use graphite_document_legacy::layers::style::PathStyle;
/// # use graphite_document_legacy::layers::layer_info::LegacyLayerType;
/// let mut folder = FolderLegacyLayer::default();
///
/// // Create two layers to be added to the folder
/// let mut shape_layer = ShapeLegacyLayer::rectangle(PathStyle::default());
/// let mut folder_layer = FolderLegacyLayer::default();
///
/// folder.add_layer(shape_layer.into(), None, -1);
/// folder.add_layer(folder_layer.into(), Some(123), 0);
/// ```
pub fn add_layer(&mut self, layer: LegacyLayer, id: Option<LayerId>, insert_index: isize) -> Option<LayerId> {
let mut insert_index = insert_index as i128;
// Bounds check for the insert index
if insert_index < 0 {
insert_index = self.layers.len() as i128 + insert_index + 1;
}
if insert_index > self.layers.len() as i128 || insert_index < 0 {
return None;
}
if let Some(id) = id {
self.next_assignment_id = id;
}
if self.layer_ids.contains(&self.next_assignment_id) {
return None;
}
let id = self.next_assignment_id;
self.layers.insert(insert_index as usize, layer);
self.layer_ids.insert(insert_index as usize, id);
// Linear probing for collision avoidance
while self.layer_ids.contains(&self.next_assignment_id) {
self.next_assignment_id += 1;
}
Some(id)
pub fn layer(&self, layer_id: LayerId) -> Option<&LegacyLayer> {
let index = self.layer_ids.iter().position(|x| *x == layer_id).ok_or_else(|| DocumentError::LayerNotFound([layer_id].into())).ok()?;
Some(&self.layers[index])
}
/// Remove a layer with a given ID from the folder.
/// This operation will fail if `id` is not present in the folder.
///
/// # Example
/// ```
/// # use graphite_document_legacy::layers::folder_layer::FolderLegacyLayer;
/// let mut folder = FolderLegacyLayer::default();
///
/// // Try to remove a layer that does not exist
/// assert!(folder.remove_layer(123).is_err());
///
/// // Add another folder to the folder
/// folder.add_layer(FolderLegacyLayer::default().into(), Some(123), -1);
///
/// // Try to remove that folder again
/// assert!(folder.remove_layer(123).is_ok());
/// assert_eq!(folder.layers().len(), 0)
/// ```
pub fn remove_layer(&mut self, id: LayerId) -> Result<(), DocumentError> {
let pos = self.position_of_layer(id)?;
self.layers.remove(pos);
self.layer_ids.remove(pos);
Ok(())
}
/// Returns a list of [LayerId]s in the folder.
pub fn list_layers(&self) -> &[LayerId] {
self.layer_ids.as_slice()
}
/// Get references to all the [Layer]s in the folder.
pub fn layers(&self) -> &[LegacyLayer] {
self.layers.as_slice()
}
/// Get mutable references to all the [Layer]s in the folder.
pub fn layers_mut(&mut self) -> &mut [LegacyLayer] {
self.layers.as_mut_slice()
}
pub fn layer(&self, id: LayerId) -> Option<&LegacyLayer> {
let pos = self.position_of_layer(id).ok()?;
Some(&self.layers[pos])
}
pub fn layer_mut(&mut self, id: LayerId) -> Option<&mut LegacyLayer> {
let pos = self.position_of_layer(id).ok()?;
Some(&mut self.layers[pos])
}
pub fn generate_new_folder_ids(&mut self) {
self.next_assignment_id = generate_uuid();
}
/// Returns `true` if the folder contains a layer with the given [LayerId].
///
/// # Example
/// ```
/// # use graphite_document_legacy::layers::folder_layer::FolderLegacyLayer;
/// let mut folder = FolderLegacyLayer::default();
///
/// // Search for an id that does not exist
/// assert!(!folder.folder_contains(123));
///
/// // Add layer with the id "123" to the folder
/// folder.add_layer(FolderLegacyLayer::default().into(), Some(123), -1);
///
/// // Search for the id "123"
/// assert!(folder.folder_contains(123));
/// ```
pub fn folder_contains(&self, id: LayerId) -> bool {
self.layer_ids.contains(&id)
}
/// Tries to find the index of a layer with the given [LayerId] within the folder.
/// This operation will fail if no layer with a matching ID is present in the folder.
///
/// # Example
/// ```
/// # use graphite_document_legacy::layers::folder_layer::FolderLegacyLayer;
/// let mut folder = FolderLegacyLayer::default();
///
/// // Search for an id that does not exist
/// assert!(folder.position_of_layer(123).is_err());
///
/// // Add layer with the id "123" to the folder
/// folder.add_layer(FolderLegacyLayer::default().into(), Some(123), -1);
/// folder.add_layer(FolderLegacyLayer::default().into(), Some(42), -1);
///
/// assert_eq!(folder.position_of_layer(123), Ok(0));
/// assert_eq!(folder.position_of_layer(42), Ok(1));
/// ```
pub fn position_of_layer(&self, layer_id: LayerId) -> Result<usize, DocumentError> {
self.layer_ids.iter().position(|x| *x == layer_id).ok_or_else(|| DocumentError::LayerNotFound([layer_id].into()))
}
/// Tries to get a reference to a folder with the given [LayerId].
/// This operation will return `None` if either no layer with `id` exists
/// in the folder, or the layer with matching ID is not a folder.
///
/// # Example
/// ```
/// # use graphite_document_legacy::layers::folder_layer::FolderLegacyLayer;
/// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer;
/// # use graphite_document_legacy::layers::style::PathStyle;
/// let mut folder = FolderLegacyLayer::default();
///
/// // Search for an id that does not exist
/// assert!(folder.folder(132).is_none());
///
/// // add a folder and search for it
/// folder.add_layer(FolderLegacyLayer::default().into(), Some(123), -1);
/// assert!(folder.folder(123).is_some());
///
/// // add a non-folder layer and search for it
/// folder.add_layer(ShapeLegacyLayer::rectangle(PathStyle::default()).into(), Some(42), -1);
/// assert!(folder.folder(42).is_none());
/// ```
pub fn folder(&self, id: LayerId) -> Option<&FolderLegacyLayer> {
match self.layer(id) {
Some(LegacyLayer {
data: LegacyLayerType::Folder(folder),
..
}) => Some(folder),
_ => None,
}
}
/// Tries to get a mutable reference to folder with the given `id`.
/// This operation will return `None` if either no layer with `id` exists
/// in the folder or the layer with matching ID is not a folder.
/// See the [FolderLegacyLayer::folder] method for a usage example.
pub fn folder_mut(&mut self, id: LayerId) -> Option<&mut FolderLegacyLayer> {
match self.layer_mut(id) {
Some(LegacyLayer {
data: LegacyLayerType::Folder(folder),
..
}) => Some(folder),
_ => None,
}
pub fn layer_mut(&mut self, layer_id: LayerId) -> Option<&mut LegacyLayer> {
let index = self.layer_ids.iter().position(|x| *x == layer_id).ok_or_else(|| DocumentError::LayerNotFound([layer_id].into())).ok()?;
Some(&mut self.layers[index])
}
}
+18 -486
View File
@@ -1,70 +1,44 @@
use super::folder_layer::FolderLegacyLayer;
use super::layer_layer::LayerLegacyLayer;
use super::shape_layer::ShapeLegacyLayer;
use super::style::{PathStyle, RenderData};
use crate::intersection::Quad;
use crate::DocumentError;
use crate::LayerId;
use graphene_core::raster::BlendMode;
use graphene_core::vector::VectorData;
use graphene_std::vector::subpath::Subpath;
use core::fmt;
use glam::{DAffine2, DMat2, DVec2};
use serde::{Deserialize, Serialize};
use std::fmt::Write;
// ===============
// LegacyLayerType
// ===============
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
/// Represents different types of layers.
pub enum LegacyLayerType {
/// A layer that wraps a [FolderLegacyLayer] struct.
Folder(FolderLegacyLayer),
/// A layer that wraps a [ShapeLegacyLayer] struct. Still used by the overlays system, but will be removed in the future.
Shape(ShapeLegacyLayer),
/// A layer that wraps an [LayerLegacyLayer] struct.
Layer(LayerLegacyLayer),
}
impl Default for LegacyLayerType {
fn default() -> Self {
LegacyLayerType::Folder(FolderLegacyLayer::default())
LegacyLayerType::Layer(Default::default())
}
}
impl LegacyLayerType {
pub fn inner(&self) -> &dyn LayerData {
match self {
LegacyLayerType::Shape(shape) => shape,
LegacyLayerType::Folder(folder) => folder,
LegacyLayerType::Layer(layer) => layer,
}
}
pub fn inner_mut(&mut self) -> &mut dyn LayerData {
match self {
LegacyLayerType::Shape(shape) => shape,
LegacyLayerType::Folder(folder) => folder,
LegacyLayerType::Layer(layer) => layer,
}
}
}
// =========================
// LayerDataTypeDiscriminant
// =========================
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, specta::Type)]
pub enum LayerDataTypeDiscriminant {
Folder,
Shape,
Layer,
Artboard,
}
impl fmt::Display for LayerDataTypeDiscriminant {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
LayerDataTypeDiscriminant::Folder => write!(f, "Folder"),
LayerDataTypeDiscriminant::Shape => write!(f, "Shape"),
LayerDataTypeDiscriminant::Layer => write!(f, "Layer"),
LayerDataTypeDiscriminant::Artboard => write!(f, "Artboard"),
}
}
}
@@ -75,385 +49,33 @@ impl From<&LegacyLayerType> for LayerDataTypeDiscriminant {
match data {
Folder(_) => LayerDataTypeDiscriminant::Folder,
Shape(_) => LayerDataTypeDiscriminant::Shape,
Layer(_) => LayerDataTypeDiscriminant::Layer,
}
}
}
// ** CONVERSIONS **
// ===========
// LegacyLayer
// ===========
impl<'a> TryFrom<&'a mut LegacyLayer> for &'a mut Subpath {
type Error = &'static str;
/// Convert a mutable layer into a mutable [Subpath].
fn try_from(layer: &'a mut LegacyLayer) -> Result<&'a mut Subpath, Self::Error> {
match &mut layer.data {
LegacyLayerType::Shape(layer) => Ok(&mut layer.shape),
_ => Err("Did not find any shape data in the layer"),
}
}
}
impl<'a> TryFrom<&'a LegacyLayer> for &'a Subpath {
type Error = &'static str;
/// Convert a reference to a layer into a reference of a [Subpath].
fn try_from(layer: &'a LegacyLayer) -> Result<&'a Subpath, Self::Error> {
match &layer.data {
LegacyLayerType::Shape(layer) => Ok(&layer.shape),
_ => Err("Did not find any shape data in the layer"),
}
}
}
/// Defines shared behavior for every layer type.
pub trait LayerData {
/// Render the layer as an SVG tag to a given string, returning a boolean to indicate if a redraw is required next frame.
///
/// # Example
/// ```
/// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer;
/// # use graphite_document_legacy::layers::style::{Fill, PathStyle, ViewMode, RenderData};
/// # use graphite_document_legacy::layers::layer_info::LayerData;
/// # use std::collections::HashMap;
///
/// let mut shape = ShapeLegacyLayer::rectangle(PathStyle::new(None, Fill::None));
/// let mut svg = String::new();
///
/// // Render the shape without any transforms, in normal view mode
/// # let font_cache = Default::default();
/// let render_data = RenderData::new(&font_cache, ViewMode::Normal, None);
/// shape.render(&mut svg, &mut String::new(), &mut vec![], &render_data);
///
/// assert_eq!(
/// svg,
/// "<g transform=\"matrix(\n1,-0,-0,1,-0,-0)\">\
/// <path d=\"M0,0L0,1L1,1L1,0Z\" fill=\"none\" />\
/// </g>"
/// );
/// ```
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<glam::DAffine2>, render_data: &RenderData) -> bool;
/// Determine the layers within this layer that intersect a given quad.
/// # Example
/// ```
/// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer;
/// # use graphite_document_legacy::layers::style::{Fill, PathStyle, ViewMode, RenderData};
/// # use graphite_document_legacy::layers::layer_info::LayerData;
/// # use graphite_document_legacy::intersection::Quad;
/// # use glam::f64::{DAffine2, DVec2};
/// # use std::collections::HashMap;
///
/// let mut shape = ShapeLegacyLayer::ellipse(PathStyle::new(None, Fill::None));
/// let shape_id = 42;
/// let mut svg = String::new();
///
/// let quad = Quad::from_box([DVec2::ZERO, DVec2::ONE]);
/// let mut intersections = vec![];
///
/// let font_cache = Default::default();
/// let render_data = RenderData::new(&font_cache, Default::default(), None);
/// shape.intersects_quad(quad, &mut vec![shape_id], &mut intersections, &render_data);
///
/// assert_eq!(intersections, vec![vec![shape_id]]);
/// ```
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, render_data: &RenderData);
// TODO: this doctest fails because 0 != 1e-32, maybe assert difference < epsilon?
/// Calculate the bounding box for the layer's contents after applying a given transform.
/// # Example
/// ```no_run
/// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer;
/// # use graphite_document_legacy::layers::style::{Fill, PathStyle, RenderData};
/// # use graphite_document_legacy::layers::layer_info::LayerData;
/// # use glam::f64::{DAffine2, DVec2};
/// # use std::collections::HashMap;
/// let shape = ShapeLegacyLayer::ellipse(PathStyle::new(None, Fill::None));
///
/// // Calculate the bounding box without applying any transformations.
/// // (The identity transform maps every vector to itself.)
/// let transform = DAffine2::IDENTITY;
/// let font_cache = Default::default();
/// let render_data = RenderData::new(&font_cache, Default::default(), None);
/// let bounding_box = shape.bounding_box(transform, &render_data);
///
/// assert_eq!(bounding_box, Some([DVec2::ZERO, DVec2::ONE]));
/// ```
fn bounding_box(&self, transform: glam::DAffine2, render_data: &RenderData) -> Option<[DVec2; 2]>;
}
impl LayerData for LegacyLayerType {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<glam::DAffine2>, render_data: &RenderData) -> bool {
self.inner_mut().render(svg, svg_defs, transforms, render_data)
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, render_data: &RenderData) {
self.inner().intersects_quad(quad, path, intersections, render_data)
}
fn bounding_box(&self, transform: glam::DAffine2, render_data: &RenderData) -> Option<[DVec2; 2]> {
self.inner().bounding_box(transform, render_data)
}
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "glam::DAffine2")]
struct DAffine2Ref {
pub matrix2: DMat2,
pub translation: DVec2,
}
/// Utility function for providing a default boolean value to serde.
#[inline(always)]
fn return_true() -> bool {
true
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)]
pub struct LegacyLayer {
/// Whether the layer is currently visible or hidden.
pub visible: bool,
/// The user-given name of the layer.
pub name: Option<String>,
/// Whether the layer is currently visible or hidden.
pub visible: bool,
/// The type of layer, such as folder or shape.
pub data: LegacyLayerType,
/// A transformation applied to the layer (translation, rotation, scaling, and shear).
#[serde(with = "DAffine2Ref")]
pub transform: glam::DAffine2,
/// Should the aspect ratio of this layer be preserved?
#[serde(default = "return_true")]
pub preserve_aspect: bool,
/// The center of transformations like rotation or scaling with the shift key.
/// This is in local space (so the layer's transform should be applied).
pub pivot: DVec2,
/// The cached SVG thumbnail view of the layer.
#[serde(skip)]
pub thumbnail_cache: String,
/// The cached SVG render of the layer.
#[serde(skip)]
pub cache: String,
/// The cached definition(s) used by the layer's SVG tag, placed at the top in the SVG defs tag.
#[serde(skip)]
pub svg_defs_cache: String,
/// Whether or not the [Cache](Layer::cache) and [Thumbnail Cache](Layer::thumbnail_cache) need to be updated.
#[serde(skip, default = "return_true")]
pub cache_dirty: bool,
/// The blend mode describing how this layer should composite with others underneath it.
pub blend_mode: BlendMode,
/// The opacity, in the range of 0 to 1.
pub opacity: f64,
}
impl Default for LegacyLayer {
fn default() -> Self {
Self {
visible: Default::default(),
name: Default::default(),
data: Default::default(),
transform: Default::default(),
preserve_aspect: Default::default(),
pivot: Default::default(),
thumbnail_cache: Default::default(),
cache: Default::default(),
svg_defs_cache: Default::default(),
cache_dirty: Default::default(),
blend_mode: Default::default(),
opacity: Default::default(),
}
}
}
impl LegacyLayer {
pub fn new(data: LegacyLayerType, transform: [f64; 6]) -> Self {
Self {
visible: true,
name: None,
data,
transform: glam::DAffine2::from_cols_array(&transform),
preserve_aspect: true,
pivot: DVec2::splat(0.5),
cache: String::new(),
thumbnail_cache: String::new(),
svg_defs_cache: String::new(),
cache_dirty: true,
blend_mode: BlendMode::Normal,
opacity: 1.,
}
}
/// Gets a child layer of this layer, by a path. If the layer with id 1 is inside a folder with id 0, the path will be [0, 1].
pub fn child(&self, path: &[LayerId]) -> Option<&LegacyLayer> {
let mut layer = self;
for id in path {
layer = layer.as_folder().ok()?.layer(*id)?;
}
Some(layer)
}
/// Gets a child layer of this layer, by a path. If the layer with id 1 is inside a folder with id 0, the path will be [0, 1].
pub fn child_mut(&mut self, path: &[LayerId]) -> Option<&mut LegacyLayer> {
let mut layer = self;
for id in path {
layer = layer.as_folder_mut().ok()?.layer_mut(*id)?;
}
Some(layer)
}
/// Iterate over the layers encapsulated by this layer.
/// If the [Layer type](Layer::data) is not a folder, the only item in the iterator will be the layer itself.
/// If the [Layer type](Layer::data) wraps a [Folder](LegacyLayerType::Folder), the iterator will recursively yield all the layers contained in the folder as well as potential sub-folders.
///
/// # Example
/// ```
/// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer;
/// # use graphite_document_legacy::layers::layer_info::Layer;
/// # use graphite_document_legacy::layers::style::PathStyle;
/// # use graphite_document_legacy::layers::folder_layer::FolderLegacyLayer;
/// let mut root_folder = FolderLegacyLayer::default();
///
/// // Add a shape to the root folder
/// let child_1: Layer = ShapeLegacyLayer::rectangle(PathStyle::default()).into();
/// root_folder.add_layer(child_1.clone(), None, -1);
///
/// // Add a folder containing another shape to the root layer
/// let mut child_folder = FolderLegacyLayer::default();
/// let grandchild: Layer = ShapeLegacyLayer::rectangle(PathStyle::default()).into();
/// child_folder.add_layer(grandchild.clone(), None, -1);
/// let child_2: Layer = child_folder.into();
/// root_folder.add_layer(child_2.clone(), None, -1);
/// let root: Layer = root_folder.into();
///
/// let mut iter = root.iter();
/// assert_eq!(iter.next(), Some(&root));
/// assert_eq!(iter.next(), Some(&child_2));
/// assert_eq!(iter.next(), Some(&grandchild));
/// assert_eq!(iter.next(), Some(&child_1));
/// assert_eq!(iter.next(), None);
/// ```
pub fn iter(&self) -> LayerIter<'_> {
LayerIter { stack: vec![self] }
}
/// Renders the layer, returning the result and if a redraw is required
pub fn render(&mut self, transforms: &mut Vec<DAffine2>, svg_defs: &mut String, render_data: &RenderData) -> (&str, bool) {
if !self.visible {
return ("", false);
}
transforms.push(self.transform);
// Skip rendering if outside the viewport bounds
if let Some(viewport_bounds) = render_data.culling_bounds {
if let Some(bounding_box) = self.data.bounding_box(transforms.iter().cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY), render_data) {
let is_overlapping =
viewport_bounds[0].x < bounding_box[1].x && bounding_box[0].x < viewport_bounds[1].x && viewport_bounds[0].y < bounding_box[1].y && bounding_box[0].y < viewport_bounds[1].y;
if !is_overlapping {
transforms.pop();
self.cache.clear();
self.cache_dirty = true;
return ("", true);
}
}
}
let mut requires_redraw = false;
if self.cache_dirty {
self.thumbnail_cache.clear();
self.svg_defs_cache.clear();
requires_redraw = self.data.render(&mut self.thumbnail_cache, &mut self.svg_defs_cache, transforms, render_data);
self.cache.clear();
let _ = writeln!(self.cache, r#"<g transform="matrix("#);
self.transform.to_cols_array().iter().enumerate().for_each(|(i, f)| {
let _ = self.cache.write_str(&(f.to_string() + if i == 5 { "" } else { "," }));
});
let _ = write!(self.cache, r#")" style="opacity: {};{}">{}</g>"#, self.opacity, self.blend_mode.render(), self.thumbnail_cache.as_str());
self.cache_dirty = false;
}
transforms.pop();
svg_defs.push_str(&self.svg_defs_cache);
// If a redraw is required then set the cache to dirty.
if requires_redraw {
self.cache_dirty = true;
}
(self.cache.as_str(), requires_redraw)
}
pub fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, render_data: &RenderData) {
if !self.visible {
return;
}
let transformed_quad = self.transform.inverse() * quad;
self.data.intersects_quad(transformed_quad, path, intersections, render_data)
}
/// Compute the bounding box of the layer after applying a transform to it.
///
/// # Example
/// ```
/// # use graphite_document_legacy::layers::shape_layer::ShapeLegacyLayer;
/// # use graphite_document_legacy::layers::layer_info::Layer;
/// # use graphite_document_legacy::layers::style::{PathStyle, RenderData};
/// # use glam::DVec2;
/// # use glam::f64::DAffine2;
/// # use std::collections::HashMap;
/// // Create a rectangle with the default dimensions, from `(0|0)` to `(1|1)`
/// let layer: Layer = ShapeLegacyLayer::rectangle(PathStyle::default()).into();
///
/// // Apply the Identity transform, which leaves the points unchanged
/// let transform = DAffine2::IDENTITY;
/// let font_cache = Default::default();
/// let render_data = RenderData::new(&font_cache, Default::default(), None);
/// assert_eq!(
/// layer.aabb_for_transform(transform, &render_data),
/// Some([DVec2::ZERO, DVec2::ONE]),
/// );
///
/// // Apply a transform that scales every point by a factor of two
/// let transform = DAffine2::from_scale(DVec2::ONE * 2.);
/// assert_eq!(
/// layer.aabb_for_transform(transform, &render_data),
/// Some([DVec2::ZERO, DVec2::ONE * 2.]),
/// );
pub fn aabb_for_transform(&self, transform: DAffine2, render_data: &RenderData) -> Option<[DVec2; 2]> {
self.data.bounding_box(transform, render_data)
}
pub fn aabb(&self, render_data: &RenderData) -> Option<[DVec2; 2]> {
self.aabb_for_transform(self.transform, render_data)
}
pub fn bounding_transform(&self, render_data: &RenderData) -> DAffine2 {
let scale = match self.aabb_for_transform(DAffine2::IDENTITY, render_data) {
Some([a, b]) => {
let dimensions = b - a;
DAffine2::from_scale(dimensions)
}
None => DAffine2::IDENTITY,
};
self.transform * scale
}
pub fn layerspace_pivot(&self, render_data: &RenderData) -> DVec2 {
let [mut min, max] = self.aabb_for_transform(DAffine2::IDENTITY, render_data).unwrap_or([DVec2::ZERO, DVec2::ONE]);
// If the layer bounds are 0 in either axis then set them to one (to avoid div 0)
if (max.x - min.x) < f64::EPSILON * 1000. {
min.x = max.x - 1.;
}
if (max.y - min.y) < f64::EPSILON * 1000. {
min.y = max.y - 1.;
}
self.pivot * (max - min) + min
}
/// Get a mutable reference to the Folder wrapped by the layer.
/// This operation will fail if the [Layer type](Layer::data) is not `LegacyLayerType::Folder`.
pub fn as_folder_mut(&mut self) -> Result<&mut FolderLegacyLayer, DocumentError> {
@@ -463,20 +85,6 @@ impl LegacyLayer {
}
}
pub fn as_vector_data(&self) -> Option<&VectorData> {
match &self.data {
LegacyLayerType::Layer(layer) => layer.as_vector_data(),
_ => None,
}
}
pub fn as_subpath_mut(&mut self) -> Option<&mut Subpath> {
match &mut self.data {
LegacyLayerType::Shape(s) => Some(&mut s.shape),
_ => None,
}
}
/// Get a reference to the Folder wrapped by the layer.
/// This operation will fail if the [Layer type](Layer::data) is not `LegacyLayerType::Folder`.
pub fn as_folder(&self) -> Result<&FolderLegacyLayer, DocumentError> {
@@ -485,87 +93,11 @@ impl LegacyLayer {
_ => Err(DocumentError::NotFolder),
}
}
/// Get a mutable reference to the NodeNetwork
/// This operation will fail if the [Layer type](Layer::data) is not `LegacyLayerType::Layer`.
pub fn as_layer_network_mut(&mut self) -> Result<&mut graph_craft::document::NodeNetwork, DocumentError> {
match &mut self.data {
LegacyLayerType::Layer(layer) => Ok(&mut layer.network),
_ => Err(DocumentError::NotLayer),
}
}
/// Get a reference to the NodeNetwork
/// This operation will fail if the [Layer type](Layer::data) is not `LegacyLayerType::Layer`.
pub fn as_layer_network(&self) -> Result<&graph_craft::document::NodeNetwork, DocumentError> {
match &self.data {
LegacyLayerType::Layer(layer) => Ok(&layer.network),
_ => Err(DocumentError::NotLayer),
}
}
pub fn as_layer(&self) -> Result<&LayerLegacyLayer, DocumentError> {
match &self.data {
LegacyLayerType::Layer(layer) => Ok(layer),
_ => Err(DocumentError::NotLayer),
}
}
pub fn style(&self) -> Result<&PathStyle, DocumentError> {
match &self.data {
LegacyLayerType::Shape(shape) => Ok(&shape.style),
LegacyLayerType::Layer(layer) => layer.as_vector_data().map(|vector| &vector.style).ok_or(DocumentError::NotShape),
_ => Err(DocumentError::NotShape),
}
}
pub fn style_mut(&mut self) -> Result<&mut PathStyle, DocumentError> {
match &mut self.data {
LegacyLayerType::Shape(s) => Ok(&mut s.style),
_ => Err(DocumentError::NotShape),
}
}
}
impl Clone for LegacyLayer {
fn clone(&self) -> Self {
Self {
visible: self.visible,
name: self.name.clone(),
data: self.data.clone(),
transform: self.transform,
preserve_aspect: self.preserve_aspect,
pivot: self.pivot,
cache: String::new(),
thumbnail_cache: String::new(),
svg_defs_cache: String::new(),
cache_dirty: true,
blend_mode: self.blend_mode,
opacity: self.opacity,
}
}
}
impl From<FolderLegacyLayer> for LegacyLayer {
fn from(from: FolderLegacyLayer) -> LegacyLayer {
LegacyLayer::new(LegacyLayerType::Folder(from), DAffine2::IDENTITY.to_cols_array())
}
}
impl From<ShapeLegacyLayer> for LegacyLayer {
fn from(from: ShapeLegacyLayer) -> LegacyLayer {
LegacyLayer::new(LegacyLayerType::Shape(from), DAffine2::IDENTITY.to_cols_array())
}
}
impl<'a> IntoIterator for &'a LegacyLayer {
type Item = &'a LegacyLayer;
type IntoIter = LayerIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
// =========
// LayerIter
// =========
/// An iterator over the layers encapsulated by this layer.
/// See [Layer::iter] for more information.
@@ -581,7 +113,7 @@ impl<'a> Iterator for LayerIter<'a> {
match self.stack.pop() {
Some(layer) => {
if let LegacyLayerType::Folder(folder) = &layer.data {
let layers = folder.layers();
let layers = folder.layers.as_slice();
self.stack.extend(layers);
};
Some(layer)
+3 -164
View File
@@ -1,172 +1,11 @@
use super::layer_info::LayerData;
use super::style::{RenderData, ViewMode};
use crate::intersection::{intersect_quad_bez_path, intersect_quad_subpath, Quad};
use crate::LayerId;
use glam::{DAffine2, DMat2, DVec2};
use graphene_core::vector::VectorData;
use graphene_core::SurfaceId;
use kurbo::{Affine, BezPath, Shape as KurboShape};
use serde::{Deserialize, Serialize};
use std::fmt::Write;
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
pub enum CachedOutputData {
#[default]
None,
BlobURL(String),
VectorPath(Box<VectorData>),
SurfaceId(SurfaceId),
Svg(String),
}
// ================
// LayerLegacyLayer
// ================
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
pub struct LayerLegacyLayer {
/// The document node network that this layer contains
pub network: graph_craft::document::NodeNetwork,
#[serde(skip)]
pub cached_output_data: CachedOutputData,
}
impl LayerData for LayerLegacyLayer {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, render_data: &RenderData) -> bool {
let transform = self.transform(transforms, render_data.view_mode);
let inverse = transform.inverse();
let (width, height) = (transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
if !inverse.is_finite() {
let _ = write!(svg, "<!-- SVG shape has an invalid transform -->");
return false;
}
let _ = writeln!(svg, r#"<g transform="matrix("#);
inverse.to_cols_array().iter().enumerate().for_each(|(i, entry)| {
let _ = svg.write_str(&(entry.to_string() + if i == 5 { "" } else { "," }));
});
let _ = svg.write_str(r#")">"#);
let matrix = (transform * DAffine2::from_scale((width, height).into()).inverse())
.to_cols_array()
.iter()
.enumerate()
.fold(String::new(), |val, (i, entry)| val + &(entry.to_string() + if i == 5 { "" } else { "," }));
// Render any paths if they exist
match &self.cached_output_data {
CachedOutputData::VectorPath(vector_data) => {
let layer_bounds = vector_data.bounding_box().unwrap_or_default();
let transformed_bounds = vector_data.bounding_box_with_transform(transform).unwrap_or_default();
let _ = write!(svg, "<path d=\"");
for subpath in &vector_data.subpaths {
let _ = subpath.subpath_to_svg(svg, transform);
}
svg.push('"');
svg.push_str(&vector_data.style.render(render_data.view_mode, svg_defs, transform, layer_bounds, transformed_bounds));
let _ = write!(svg, "/>");
}
CachedOutputData::BlobURL(blob_url) => {
// Render the image if it exists
let _ = write!(
svg,
r#"<image width="{}" height="{}" preserveAspectRatio="none" href="{}" transform="matrix({})" />"#,
width.abs(),
height.abs(),
blob_url,
matrix
);
}
CachedOutputData::SurfaceId(SurfaceId(id)) => {
// Render the image if it exists
let _ = write!(
svg,
r#"
<foreignObject width="{}" height="{}" transform="matrix({})"><div data-canvas-placeholder="canvas{}"></div></foreignObject>
"#,
width.abs(),
height.abs(),
matrix,
id
);
}
CachedOutputData::Svg(new_svg) => svg.push_str(new_svg),
_ => {
// Render a dotted blue outline if there is no image or vector data
let _ = write!(
svg,
r#"<rect width="{}" height="{}" fill="none" stroke="var(--color-data-vector)" stroke-width="3" stroke-dasharray="8" transform="matrix({})" />"#,
width.abs(),
height.abs(),
matrix,
);
}
}
let _ = svg.write_str(r#"</g>"#);
false
}
fn bounding_box(&self, transform: glam::DAffine2, _render_data: &RenderData) -> Option<[DVec2; 2]> {
if let CachedOutputData::VectorPath(vector_data) = &self.cached_output_data {
return vector_data.bounding_box_with_transform(transform);
}
let mut path = self.bounds();
if transform.matrix2 == DMat2::ZERO {
return None;
}
path.apply_affine(glam_to_kurbo(transform));
let kurbo::Rect { x0, y0, x1, y1 } = path.bounding_box();
Some([(x0, y0).into(), (x1, y1).into()])
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _render_data: &RenderData) {
if let CachedOutputData::VectorPath(vector_data) = &self.cached_output_data {
let filled_style = vector_data.style.fill().is_some();
if vector_data.subpaths.iter().any(|subpath| intersect_quad_subpath(quad, subpath, filled_style || subpath.closed())) {
intersections.push(path.clone());
}
} else if intersect_quad_bez_path(quad, &self.bounds(), true) {
intersections.push(path.clone());
}
}
}
impl LayerLegacyLayer {
pub fn transform(&self, transforms: &[DAffine2], mode: ViewMode) -> DAffine2 {
let start = match mode {
ViewMode::Outline => 0,
_ => (transforms.len() as i32 - 1).max(0) as usize,
};
transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY)
}
fn bounds(&self) -> BezPath {
kurbo::Rect::from_origin_size(kurbo::Point::ZERO, kurbo::Size::new(1., 1.)).to_path(0.)
}
pub fn as_vector_data(&self) -> Option<&VectorData> {
if let CachedOutputData::VectorPath(vector_data) = &self.cached_output_data {
Some(vector_data)
} else {
None
}
}
pub fn as_blob_url(&self) -> Option<&String> {
if let CachedOutputData::BlobURL(blob_url) = &self.cached_output_data {
Some(blob_url)
} else {
None
}
}
}
fn glam_to_kurbo(transform: DAffine2) -> Affine {
Affine::new(transform.to_cols_array())
}
-13
View File
@@ -3,7 +3,6 @@
//! Layers allow the user to mutate part of the document while leaving the rest unchanged.
//! There are currently these different types of layers:
//! * [Folder layers](folder_layer::FolderLegacyLayer), which encapsulate sub-layers
//! * [Shape layers](shape_layer::ShapeLegacyLayer), which contain generic SVG [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)s (deprecated but still used by the overlays system).
//! * [Layer layers](layer_layer::LayerLegacyLayer), which contain a node graph layer
//!
//! Refer to the module-level documentation for detailed information on each layer.
@@ -13,21 +12,9 @@
//! When different layers overlap, they are blended together according to the [BlendMode](blend_mode::BlendMode)
//! using the CSS [`mix-blend-mode`](https://developer.mozilla.org/en-US/docs/Web/CSS/mix-blend-mode) property and the layer opacity.
pub mod base64_serde;
/// Contains the [FolderLegacyLayer](folder_layer::FolderLegacyLayer) type that encapsulates other layers, including more folders.
pub mod folder_layer;
/// Contains the base [Layer](layer_info::Layer) type, an abstraction over the different types of layers.
pub mod layer_info;
/// Contains the [LayerLegacyLayer](nodegraph_layer::LayerLegacyLayer) type that contains a node graph.
pub mod layer_layer;
// TODO: Remove shape layers after rewriting the overlay system
/// Contains the [ShapeLegacyLayer](shape_layer::ShapeLegacyLayer) type, a generic SVG element defined using Bezier paths.
pub mod shape_layer;
mod render_data;
pub use render_data::RenderData;
pub mod style {
pub use super::RenderData;
pub use graphene_core::vector::style::*;
}
-22
View File
@@ -1,22 +0,0 @@
use super::style::ViewMode;
use graphene_std::text::FontCache;
use glam::DVec2;
/// Contains metadata for rendering the document as an svg
#[derive(Debug, Clone, Copy)]
pub struct RenderData<'a> {
pub font_cache: &'a FontCache,
pub view_mode: ViewMode,
pub culling_bounds: Option<[DVec2; 2]>,
}
impl<'a> RenderData<'a> {
pub fn new(font_cache: &'a FontCache, view_mode: ViewMode, culling_bounds: Option<[DVec2; 2]>) -> Self {
Self {
font_cache,
view_mode,
culling_bounds,
}
}
}
-176
View File
@@ -1,176 +0,0 @@
use super::layer_info::LayerData;
use super::style::{self, PathStyle, RenderData, ViewMode};
use crate::intersection::{intersect_quad_bez_path, Quad};
use crate::LayerId;
use graphene_std::vector::subpath::Subpath;
use glam::{DAffine2, DMat2, DVec2};
use serde::{Deserialize, Serialize};
use std::fmt::Write;
/// A generic SVG element defined using Bezier paths.
/// Shapes are rendered as
/// [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)
/// elements inside a
/// [`<g>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g)
/// group that the transformation matrix is applied to.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, specta::Type)]
pub struct ShapeLegacyLayer {
/// The geometry of the layer.
pub shape: Subpath,
/// The visual style of the shape.
pub style: style::PathStyle,
// TODO: We might be able to remove this in a future refactor
pub render_index: i32,
}
impl LayerData for ShapeLegacyLayer {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, render_data: &RenderData) -> bool {
let mut subpath = self.shape.clone();
let layer_bounds = subpath.bounding_box().unwrap_or_default();
let transform = self.transform(transforms, render_data.view_mode);
if !transform.is_finite() || transform.matrix2.determinant() == 0. {
let _ = write!(svg, "<!-- SVG shape has an invalid transform -->");
return false;
}
let inverse = transform.inverse();
subpath.apply_affine(transform);
let transformed_bounds = subpath.bounding_box().unwrap_or_default();
let _ = writeln!(svg, r#"<g transform="matrix("#);
inverse.to_cols_array().iter().enumerate().for_each(|(i, entry)| {
let _ = svg.write_str(&(entry.to_string() + if i == 5 { "" } else { "," }));
});
let _ = svg.write_str(r#")">"#);
let _ = write!(
svg,
r#"<path d="{}" {} />"#,
subpath.to_svg(),
self.style.render(render_data.view_mode, svg_defs, transform, layer_bounds, transformed_bounds)
);
let _ = svg.write_str("</g>");
false
}
fn bounding_box(&self, transform: glam::DAffine2, _render_data: &RenderData) -> Option<[DVec2; 2]> {
let mut subpath = self.shape.clone();
if transform.matrix2 == DMat2::ZERO || !transform.is_finite() {
return None;
}
subpath.apply_affine(transform);
subpath.bounding_box()
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _render_data: &RenderData) {
let filled = self.style.fill().is_some() || self.shape.manipulator_groups().last().filter(|manipulator_group| manipulator_group.is_close()).is_some();
if intersect_quad_bez_path(quad, &(&self.shape).into(), filled) {
intersections.push(path.clone());
}
}
}
impl ShapeLegacyLayer {
/// Construct a new [ShapeLegacyLayer] with the specified [Subpath] and [PathStyle]
pub fn new(shape: Subpath, style: PathStyle) -> Self {
Self { shape, style, render_index: 1 }
}
pub fn transform(&self, transforms: &[DAffine2], mode: ViewMode) -> DAffine2 {
let start = match (mode, self.render_index) {
(ViewMode::Outline, _) => 0,
(_, -1) => 0,
(_, x) => (transforms.len() as i32 - x).max(0) as usize,
};
transforms.iter().skip(start).fold(DAffine2::IDENTITY, |a, b| a * *b)
}
// TODO The behavior of ngon changed from the previous iteration slightly, match original behavior
/// Create an N-gon.
///
/// # Panics
/// This function panics if `sides` is zero.
pub fn ngon(sides: u32, style: PathStyle) -> Self {
use std::f64::consts::{FRAC_PI_2, TAU};
fn unit_rotation(theta: f64) -> DVec2 {
DVec2::new(theta.sin(), theta.cos())
}
let mut path = kurbo::BezPath::new();
let apothem_offset_angle = TAU / (sides as f64);
// Rotate odd sided shapes by 90 degrees
let offset = ((sides + 1) % 2) as f64 * FRAC_PI_2;
let relative_points = (0..sides).map(|i| apothem_offset_angle * i as f64 + offset).map(unit_rotation);
let min = relative_points.clone().reduce(|a, b| a.min(b)).unwrap_or_default();
let transform = DAffine2::from_scale_angle_translation(DVec2::ONE / 2., 0., -min / 2.);
let point = |vec: DVec2| kurbo::Point::new(vec.x, vec.y);
let mut relative_points = relative_points.map(|p| point(transform.transform_point2(p)));
path.move_to(relative_points.next().expect("Tried to create an ngon with 0 sides"));
relative_points.for_each(|p| path.line_to(p));
path.close_path();
Self {
shape: Subpath::new_ngon(DVec2::new(0., 0.), sides.into(), 1.),
style,
render_index: 1,
}
}
/// Create a rectangular shape.
pub fn rectangle(style: PathStyle) -> Self {
Self {
shape: Subpath::new_rect(DVec2::new(0., 0.), DVec2::new(1., 1.)),
style,
render_index: 1,
}
}
/// Create an elliptical shape.
pub fn ellipse(style: PathStyle) -> Self {
Self {
shape: Subpath::new_ellipse(DVec2::new(0., 0.), DVec2::new(1., 1.)),
style,
render_index: 1,
}
}
/// Create a straight line from (0, 0) to (1, 0).
pub fn line(style: PathStyle) -> Self {
Self {
shape: Subpath::new_line(DVec2::new(0., 0.), DVec2::new(1., 0.)),
style,
render_index: 1,
}
}
/// Create a polygonal line that visits each provided point.
pub fn poly_line(points: Vec<impl Into<glam::DVec2>>, style: PathStyle) -> Self {
Self {
shape: Subpath::new_poly_line(points),
style,
render_index: 0,
}
}
/// Creates a smooth bezier spline that passes through all given points.
/// The algorithm used in this implementation is described here: <https://www.particleincell.com/2012/bezier-splines/>
pub fn spline(points: Vec<impl Into<glam::DVec2>>, style: PathStyle) -> Self {
Self {
shape: Subpath::new_spline(points),
style,
render_index: 0,
}
}
}