Refactor font_cache into render_data; delete image layer type

This commit is contained in:
Keavon Chambers
2023-01-29 20:31:14 -08:00
parent b47ec1b356
commit 8a9b9e7c3c
41 changed files with 395 additions and 554 deletions

View File

@@ -1,12 +1,11 @@
use crate::boolean_ops::composite_boolean_operation;
use crate::intersection::Quad;
use crate::layers::folder_layer::FolderLayer;
use crate::layers::image_layer::ImageLayer;
use crate::layers::layer_info::{Layer, LayerData, LayerDataType, LayerDataTypeDiscriminant};
use crate::layers::nodegraph_layer::NodeGraphFrameLayer;
use crate::layers::shape_layer::ShapeLayer;
use crate::layers::style::RenderData;
use crate::layers::text_layer::{Font, FontCache, TextLayer};
use crate::layers::text_layer::{Font, TextLayer};
use crate::{DocumentError, DocumentResponse, Operation};
use graphene_std::vector::subpath::Subpath;
@@ -49,7 +48,7 @@ impl Default for Document {
impl Document {
/// Wrapper around render, that returns the whole document as a Response.
pub fn render_root(&mut self, render_data: RenderData) -> String {
pub fn render_root(&mut self, render_data: &RenderData) -> String {
// Render and append to the defs section
let mut svg_defs = String::from("<defs>");
self.root.render(&mut vec![], &mut svg_defs, render_data);
@@ -62,7 +61,7 @@ impl Document {
}
/// Renders everything below the given layer contained within its parent folder.
pub fn render_layers_below(&mut self, below_layer_path: &[LayerId], render_data: RenderData) -> Option<String> {
pub fn render_layers_below(&mut self, below_layer_path: &[LayerId], render_data: &RenderData) -> Option<String> {
// Split the path into the layer ID and its parent folder
let (layer_id_to_render_below, parent_folder_path) = below_layer_path.split_last()?;
@@ -89,7 +88,7 @@ impl Document {
}
/// Renders a layer and its children
pub fn render_layer(&mut self, layer_path: &[LayerId], render_data: RenderData) -> Option<String> {
pub fn render_layer(&mut self, layer_path: &[LayerId], render_data: &RenderData) -> Option<String> {
// Note: it is bad practice to directly clone and modify the document structure, this is a temporary hack until this whole system is replaced by the node graph
let mut temp_clone = self.layer_mut(layer_path).ok()?.clone();
@@ -109,14 +108,14 @@ impl Document {
}
/// Checks whether each layer under `path` intersects with the provided `quad` and adds all intersection layers as paths to `intersections`.
pub fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, font_cache: &FontCache) {
self.layer(path).unwrap().intersects_quad(quad, path, intersections, font_cache);
pub fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, render_data: &RenderData) {
self.layer(path).unwrap().intersects_quad(quad, path, intersections, render_data);
}
/// Checks whether each layer under the root path intersects with the provided `quad` and returns the paths to all intersecting layers.
pub fn intersects_quad_root(&self, quad: Quad, font_cache: &FontCache) -> Vec<Vec<LayerId>> {
pub fn intersects_quad_root(&self, quad: Quad, render_data: &RenderData) -> Vec<Vec<LayerId>> {
let mut intersections = Vec::new();
self.intersects_quad(quad, &mut vec![], &mut intersections, font_cache);
self.intersects_quad(quad, &mut vec![], &mut intersections, render_data);
intersections
}
@@ -407,32 +406,32 @@ impl Document {
Ok(())
}
pub fn viewport_bounding_box(&self, path: &[LayerId], font_cache: &FontCache) -> Result<Option<[DVec2; 2]>, DocumentError> {
pub fn viewport_bounding_box(&self, path: &[LayerId], render_data: &RenderData) -> Result<Option<[DVec2; 2]>, DocumentError> {
let layer = self.layer(path)?;
let transform = self.multiply_transforms(path)?;
Ok(layer.data.bounding_box(transform, font_cache))
Ok(layer.data.bounding_box(transform, render_data))
}
pub fn bounding_box_and_transform(&self, path: &[LayerId], font_cache: &FontCache) -> Result<Option<([DVec2; 2], DAffine2)>, DocumentError> {
pub fn bounding_box_and_transform(&self, path: &[LayerId], render_data: &RenderData) -> Result<Option<([DVec2; 2], DAffine2)>, DocumentError> {
let layer = self.layer(path)?;
let transform = self.multiply_transforms(&path[..path.len() - 1])?;
Ok(layer.data.bounding_box(layer.transform, font_cache).map(|bounds| (bounds, transform)))
Ok(layer.data.bounding_box(layer.transform, render_data).map(|bounds| (bounds, transform)))
}
/// Compute the center of transformation multiplied with `Document::multiply_transforms`.
pub fn pivot(&self, path: &[LayerId], font_cache: &FontCache) -> Option<DVec2> {
pub fn pivot(&self, path: &[LayerId], render_data: &RenderData) -> Option<DVec2> {
let layer = self.layer(path).ok()?;
Some(self.multiply_transforms(path).unwrap_or_default().transform_point2(layer.layerspace_pivot(font_cache)))
Some(self.multiply_transforms(path).unwrap_or_default().transform_point2(layer.layerspace_pivot(render_data)))
}
pub fn visible_layers_bounding_box(&self, font_cache: &FontCache) -> Option<[DVec2; 2]> {
pub fn visible_layers_bounding_box(&self, render_data: &RenderData) -> Option<[DVec2; 2]> {
let mut paths = vec![];
self.visible_layers(&mut vec![], &mut paths).ok()?;
self.combined_viewport_bounding_box(paths.iter().map(|x| x.as_slice()), font_cache)
self.combined_viewport_bounding_box(paths.iter().map(|x| x.as_slice()), render_data)
}
pub fn combined_viewport_bounding_box<'a>(&self, paths: impl Iterator<Item = &'a [LayerId]>, font_cache: &FontCache) -> Option<[DVec2; 2]> {
let boxes = paths.filter_map(|path| self.viewport_bounding_box(path, font_cache).ok()?);
pub fn combined_viewport_bounding_box<'a>(&self, paths: impl Iterator<Item = &'a [LayerId]>, render_data: &RenderData) -> Option<[DVec2; 2]> {
let boxes = paths.filter_map(|path| self.viewport_bounding_box(path, render_data).ok()?);
boxes.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])])
}
@@ -552,7 +551,7 @@ impl Document {
/// Mutate the document by applying the `operation` to it. If the operation necessitates a
/// reaction from the frontend, responses may be returned.
pub fn handle_operation(&mut self, operation: Operation, font_cache: &FontCache) -> Result<Option<Vec<DocumentResponse>>, DocumentError> {
pub fn handle_operation(&mut self, operation: Operation, render_data: &RenderData) -> Result<Option<Vec<DocumentResponse>>, DocumentError> {
use DocumentResponse::*;
operation.pseudo_hash().hash(&mut self.state_identifier);
@@ -590,7 +589,7 @@ impl Document {
font_style,
} => {
let font = Font::new(font_name, font_style);
let layer_text = TextLayer::new(text, style, size, font, font_cache);
let layer_text = TextLayer::new(text, style, size, font, render_data);
let layer_data = LayerDataType::Text(layer_text);
let layer = Layer::new(layer_data, transform);
@@ -598,20 +597,6 @@ impl Document {
Some([vec![DocumentChanged, CreatedLayer { path: path.clone() }], update_thumbnails_upstream(&path)].concat())
}
Operation::AddImage {
path,
transform,
insert_index,
image_data,
mime,
} => {
let image_data = std::sync::Arc::new(image_data);
let layer = Layer::new(LayerDataType::Image(ImageLayer::new(mime, image_data)), transform);
self.set_layer(&path, layer, insert_index)?;
Some([vec![DocumentChanged, CreatedLayer { path: path.clone() }], update_thumbnails_upstream(&path)].concat())
}
Operation::AddNodeGraphFrame {
path,
insert_index,
@@ -658,7 +643,7 @@ impl Document {
.layer_mut(id)
.ok_or_else(|| DocumentError::LayerNotFound(path.clone()))?
.as_text_mut()?
.update_text(new_text, font_cache);
.update_text(new_text, render_data);
self.mark_as_dirty(&path)?;
@@ -808,7 +793,7 @@ impl Document {
text.font = Font::new(font_family, font_style);
text.size = size;
text.cached_path = Some(text.generate_path(text.load_face(font_cache)));
text.cached_path = Some(text.generate_path(text.load_face(render_data)));
self.mark_as_dirty(&path)?;
Some([vec![DocumentChanged, LayerChanged { path: path.clone() }], update_thumbnails_upstream(&path)].concat())
}
@@ -836,18 +821,14 @@ impl Document {
Some([vec![DocumentChanged], update_thumbnails_upstream(&path)].concat())
}
Operation::SetLayerBlobUrl { layer_path, blob_url, resolution } => {
let layer = self.layer_mut(&layer_path).unwrap_or_else(|_| panic!("Blob url for invalid layer with path '{:?}'", layer_path));
match &mut layer.data {
LayerDataType::Image(image) => {
image.blob_url = Some(blob_url);
image.dimensions = resolution.into();
}
LayerDataType::NodeGraphFrame(node_graph_frame) => {
node_graph_frame.blob_url = Some(blob_url);
node_graph_frame.dimensions = resolution.into();
}
_ => panic!("Incorrectly trying to set the image blob URL for a layer that is not an Image, NodeGraphFrame or Imaginate layer type"),
}
let layer = self.layer_mut(&layer_path).unwrap_or_else(|_| panic!("Blob URL for invalid layer with path '{:?}'", layer_path));
let LayerDataType::NodeGraphFrame(node_graph_frame) = &mut layer.data else {
panic!("Incorrectly trying to set the image blob URL for a layer that is not a NodeGraphFrame layer type");
};
node_graph_frame.blob_url = Some(blob_url);
node_graph_frame.dimensions = resolution.into();
self.mark_as_dirty(&layer_path)?;
Some([vec![DocumentChanged, LayerChanged { path: layer_path.clone() }], update_thumbnails_upstream(&layer_path)].concat())
@@ -1099,7 +1080,7 @@ impl Document {
// Delete the layer if there are no longer any manipulator groups
if (shape.manipulator_groups().len() - 1) == 0 {
// Delegate deletion to DeleteLayer to update Layer Tree in frontend
match self.handle_operation(Operation::DeleteLayer { path: layer_path.clone() }, font_cache) {
match self.handle_operation(Operation::DeleteLayer { path: layer_path.clone() }, render_data) {
Ok(Some(delete_responses)) => {
responses.extend(delete_responses);
responses.push(DocumentResponse::DeletedSelectedManipulatorPoints);
@@ -1169,8 +1150,8 @@ fn update_thumbnails_upstream(path: &[LayerId]) -> Vec<DocumentResponse> {
responses
}
pub fn pick_layer_safe_imaginate_resolution(layer: &Layer, font_cache: &FontCache) -> (u64, u64) {
let layer_bounds = layer.bounding_transform(font_cache);
pub fn pick_layer_safe_imaginate_resolution(layer: &Layer, render_data: &RenderData) -> (u64, u64) {
let layer_bounds = layer.bounding_transform(render_data);
let layer_bounds_size = (layer_bounds.transform_vector2((1., 0.).into()).length(), layer_bounds.transform_vector2((0., 1.).into()).length());
pick_safe_imaginate_resolution(layer_bounds_size)

View File

@@ -1,7 +1,6 @@
use super::layer_info::{Layer, LayerData, LayerDataType};
use super::style::RenderData;
use crate::intersection::Quad;
use crate::layers::text_layer::FontCache;
use crate::{DocumentError, LayerId};
use glam::DVec2;
@@ -21,7 +20,7 @@ pub struct FolderLayer {
}
impl LayerData for FolderLayer {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<glam::DAffine2>, render_data: RenderData) -> bool {
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);
@@ -31,18 +30,18 @@ impl LayerData for FolderLayer {
any_child_requires_redraw
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, font_cache: &FontCache) {
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, font_cache);
layer.intersects_quad(quad, path, intersections, render_data);
path.pop();
}
}
fn bounding_box(&self, transform: glam::DAffine2, font_cache: &FontCache) -> Option<[DVec2; 2]> {
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, font_cache))
.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])])
}
}

View File

@@ -1,116 +0,0 @@
use super::base64_serde;
use super::layer_info::LayerData;
use super::style::{RenderData, ViewMode};
use crate::intersection::{intersect_quad_bez_path, Quad};
use crate::layers::text_layer::FontCache;
use crate::LayerId;
use glam::{DAffine2, DMat2, DVec2};
use kurbo::{Affine, BezPath, Shape as KurboShape};
use serde::{Deserialize, Serialize};
use std::fmt::Write;
#[derive(Clone, PartialEq, Deserialize, Serialize, specta::Type)]
pub struct ImageLayer {
pub mime: String,
#[serde(serialize_with = "base64_serde::as_base64", deserialize_with = "base64_serde::from_base64")]
#[specta(type = String)]
pub image_data: std::sync::Arc<Vec<u8>>,
// TODO: Have the browser dispose of this blob URL when this is dropped (like when the layer is deleted)
#[serde(skip)]
pub blob_url: Option<String>,
#[serde(skip)]
pub dimensions: DVec2,
}
impl LayerData for ImageLayer {
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();
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 svg_transform = transform
.to_cols_array()
.iter()
.enumerate()
.map(|(i, entry)| entry.to_string() + if i == 5 { "" } else { "," })
.collect::<String>();
let _ = write!(
svg,
r#"<image width="{}" height="{}" transform="matrix({})" href="{}"/>"#,
self.dimensions.x,
self.dimensions.y,
svg_transform,
self.blob_url.as_ref().unwrap_or(&String::new())
);
let _ = svg.write_str("</g>");
false
}
fn bounding_box(&self, transform: glam::DAffine2, _font_cache: &FontCache) -> Option<[DVec2; 2]> {
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>>, _font_cache: &FontCache) {
if intersect_quad_bez_path(quad, &self.bounds(), true) {
intersections.push(path.clone());
}
}
}
impl ImageLayer {
pub fn new(mime: String, image_data: std::sync::Arc<Vec<u8>>) -> Self {
Self {
mime,
image_data,
blob_url: None,
dimensions: DVec2::ONE,
}
}
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(self.dimensions.x, self.dimensions.y)).to_path(0.)
}
}
impl std::fmt::Debug for ImageLayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ImageLayer")
.field("mime", &self.mime)
.field("image_data", &"...")
.field("blob_url", &self.blob_url)
.field("dimensions", &self.dimensions)
.finish()
}
}
fn glam_to_kurbo(transform: DAffine2) -> Affine {
Affine::new(transform.to_cols_array())
}

View File

@@ -1,12 +1,10 @@
use super::blend_mode::BlendMode;
use super::folder_layer::FolderLayer;
use super::image_layer::ImageLayer;
use super::nodegraph_layer::NodeGraphFrameLayer;
use super::shape_layer::ShapeLayer;
use super::style::{PathStyle, RenderData};
use super::text_layer::TextLayer;
use crate::intersection::Quad;
use crate::layers::text_layer::FontCache;
use crate::DocumentError;
use crate::LayerId;
@@ -26,8 +24,6 @@ pub enum LayerDataType {
Shape(ShapeLayer),
/// A layer that wraps a [TextLayer] struct.
Text(TextLayer),
/// A layer that wraps an [ImageLayer] struct.
Image(ImageLayer),
/// A layer that wraps an [NodeGraphFrameLayer] struct.
NodeGraphFrame(NodeGraphFrameLayer),
}
@@ -38,7 +34,6 @@ impl LayerDataType {
LayerDataType::Shape(s) => s,
LayerDataType::Folder(f) => f,
LayerDataType::Text(t) => t,
LayerDataType::Image(i) => i,
LayerDataType::NodeGraphFrame(n) => n,
}
}
@@ -48,7 +43,6 @@ impl LayerDataType {
LayerDataType::Shape(s) => s,
LayerDataType::Folder(f) => f,
LayerDataType::Text(t) => t,
LayerDataType::Image(i) => i,
LayerDataType::NodeGraphFrame(n) => n,
}
}
@@ -83,7 +77,6 @@ impl From<&LayerDataType> for LayerDataTypeDiscriminant {
Folder(_) => LayerDataTypeDiscriminant::Folder,
Shape(_) => LayerDataTypeDiscriminant::Shape,
Text(_) => LayerDataTypeDiscriminant::Text,
Image(_) => LayerDataTypeDiscriminant::Image,
NodeGraphFrame(_) => LayerDataTypeDiscriminant::NodeGraphFrame,
}
}
@@ -133,8 +126,8 @@ pub trait LayerData {
///
/// // Render the shape without any transforms, in normal view mode
/// # let font_cache = Default::default();
/// let render_data = RenderData::new(ViewMode::Normal, &font_cache, None);
/// shape.render(&mut svg, &mut String::new(), &mut vec![], render_data);
/// let render_data = RenderData::new(&font_cache, ViewMode::Normal, None);
/// shape.render(&mut svg, &mut String::new(), &mut vec![], &render_data);
///
/// assert_eq!(
/// svg,
@@ -143,13 +136,13 @@ pub trait LayerData {
/// </g>"
/// );
/// ```
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<glam::DAffine2>, render_data: RenderData) -> bool;
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::ShapeLayer;
/// # use graphite_document_legacy::layers::style::{Fill, PathStyle, ViewMode};
/// # 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};
@@ -162,18 +155,20 @@ pub trait LayerData {
/// let quad = Quad::from_box([DVec2::ZERO, DVec2::ONE]);
/// let mut intersections = vec![];
///
/// shape.intersects_quad(quad, &mut vec![shape_id], &mut intersections, &Default::default());
/// 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>>, font_cache: &FontCache);
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::ShapeLayer;
/// # use graphite_document_legacy::layers::style::{Fill, PathStyle};
/// # 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;
@@ -182,24 +177,26 @@ pub trait LayerData {
/// // Calculate the bounding box without applying any transformations.
/// // (The identity transform maps every vector to itself.)
/// let transform = DAffine2::IDENTITY;
/// let bounding_box = shape.bounding_box(transform, &Default::default());
/// 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, font_cache: &FontCache) -> Option<[DVec2; 2]>;
fn bounding_box(&self, transform: glam::DAffine2, render_data: &RenderData) -> Option<[DVec2; 2]>;
}
impl LayerData for LayerDataType {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<glam::DAffine2>, render_data: RenderData) -> bool {
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>>, font_cache: &FontCache) {
self.inner().intersects_quad(quad, path, intersections, font_cache)
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, font_cache: &FontCache) -> Option<[DVec2; 2]> {
self.inner().bounding_box(transform, font_cache)
fn bounding_box(&self, transform: glam::DAffine2, render_data: &RenderData) -> Option<[DVec2; 2]> {
self.inner().bounding_box(transform, render_data)
}
}
@@ -305,7 +302,7 @@ impl Layer {
}
/// 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) {
pub fn render(&mut self, transforms: &mut Vec<DAffine2>, svg_defs: &mut String, render_data: &RenderData) -> (&str, bool) {
if !self.visible {
return ("", false);
}
@@ -314,10 +311,7 @@ impl Layer {
// 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.font_cache)
{
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 {
@@ -363,13 +357,13 @@ impl Layer {
(self.cache.as_str(), requires_redraw)
}
pub fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, font_cache: &FontCache) {
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, font_cache)
self.data.intersects_quad(transformed_quad, path, intersections, render_data)
}
/// Compute the bounding box of the layer after applying a transform to it.
@@ -378,7 +372,7 @@ impl Layer {
/// ```
/// # use graphite_document_legacy::layers::shape_layer::ShapeLayer;
/// # use graphite_document_legacy::layers::layer_info::Layer;
/// # use graphite_document_legacy::layers::style::PathStyle;
/// # use graphite_document_legacy::layers::style::{PathStyle, RenderData};
/// # use glam::DVec2;
/// # use glam::f64::DAffine2;
/// # use std::collections::HashMap;
@@ -386,27 +380,30 @@ impl Layer {
/// let layer: Layer = ShapeLayer::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(DAffine2::IDENTITY, &Default::default()),
/// 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, &Default::default()),
/// layer.aabb_for_transform(transform, &render_data),
/// Some([DVec2::ZERO, DVec2::ONE * 2.]),
/// );
pub fn aabb_for_transform(&self, transform: DAffine2, font_cache: &FontCache) -> Option<[DVec2; 2]> {
self.data.bounding_box(transform, font_cache)
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, font_cache: &FontCache) -> Option<[DVec2; 2]> {
self.aabb_for_transform(self.transform, font_cache)
pub fn aabb(&self, render_data: &RenderData) -> Option<[DVec2; 2]> {
self.aabb_for_transform(self.transform, render_data)
}
pub fn bounding_transform(&self, font_cache: &FontCache) -> DAffine2 {
let scale = match self.aabb_for_transform(DAffine2::IDENTITY, font_cache) {
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)
@@ -417,8 +414,8 @@ impl Layer {
self.transform * scale
}
pub fn layerspace_pivot(&self, font_cache: &FontCache) -> DVec2 {
let [mut min, max] = self.aabb_for_transform(DAffine2::IDENTITY, font_cache).unwrap_or([DVec2::ZERO, DVec2::ONE]);
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. {
@@ -560,12 +557,6 @@ impl From<TextLayer> for Layer {
}
}
impl From<ImageLayer> for Layer {
fn from(from: ImageLayer) -> Layer {
Layer::new(LayerDataType::Image(from), DAffine2::IDENTITY.to_cols_array())
}
}
impl<'a> IntoIterator for &'a Layer {
type Item = &'a Layer;
type IntoIter = LayerIter<'a>;

View File

@@ -5,8 +5,7 @@
//! * [Folder layers](folder_layer::FolderLayer), which encapsulate sub-layers
//! * [Shape layers](shape_layer::ShapeLayer), which contain generic SVG [`<path>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path)s
//! * [Text layers](text_layer::TextLayer), which contain a description of laid out text
//! * [Image layers](image_layer::ImageLayer), which contain a bitmap image
//! * [Nodegraph layers](nodegraph_layer::NodegraphLayer), which contain a node graph frame
//! * [Node Graph layers](nodegraph_layer::NodegraphLayer), which contain a node graph frame
//!
//! Refer to the module-level documentation for detailed information on each layer.
//!
@@ -20,8 +19,6 @@ pub mod base64_serde;
pub mod blend_mode;
/// Contains the [FolderLayer](folder_layer::FolderLayer) type that encapsulates other layers, including more folders.
pub mod folder_layer;
/// Contains the [ImageLayer](image_layer::ImageLayer) type that contains a bitmap image.
pub mod image_layer;
/// Contains the base [Layer](layer_info::Layer) type, an abstraction over the different types of layers.
pub mod layer_info;
/// Contains the [NodegraphLayer](nodegraph_layer::NodegraphLayer) type that contains a node graph.

View File

@@ -2,7 +2,6 @@ use super::base64_serde;
use super::layer_info::LayerData;
use super::style::{RenderData, ViewMode};
use crate::intersection::{intersect_quad_bez_path, Quad};
use crate::layers::text_layer::FontCache;
use crate::LayerId;
use glam::{DAffine2, DMat2, DVec2};
@@ -34,7 +33,7 @@ pub struct ImageData {
}
impl LayerData for NodeGraphFrameLayer {
fn render(&mut self, svg: &mut String, _svg_defs: &mut String, transforms: &mut Vec<DAffine2>, render_data: RenderData) -> bool {
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();
@@ -81,7 +80,7 @@ impl LayerData for NodeGraphFrameLayer {
false
}
fn bounding_box(&self, transform: glam::DAffine2, _font_cache: &FontCache) -> Option<[DVec2; 2]> {
fn bounding_box(&self, transform: glam::DAffine2, _render_data: &RenderData) -> Option<[DVec2; 2]> {
let mut path = self.bounds();
if transform.matrix2 == DMat2::ZERO {
@@ -93,7 +92,7 @@ impl LayerData for NodeGraphFrameLayer {
Some([(x0, y0).into(), (x1, y1).into()])
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _font_cache: &FontCache) {
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _render_data: &RenderData) {
if intersect_quad_bez_path(quad, &self.bounds(), true) {
intersections.push(path.clone());
}

View File

@@ -1,7 +1,6 @@
use super::layer_info::LayerData;
use super::style::{self, PathStyle, RenderData, ViewMode};
use crate::intersection::{intersect_quad_bez_path, Quad};
use crate::layers::text_layer::FontCache;
use crate::LayerId;
use graphene_std::vector::subpath::Subpath;
@@ -27,7 +26,7 @@ pub struct ShapeLayer {
}
impl LayerData for ShapeLayer {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, render_data: RenderData) -> bool {
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();
@@ -58,7 +57,7 @@ impl LayerData for ShapeLayer {
false
}
fn bounding_box(&self, transform: glam::DAffine2, _font_cache: &FontCache) -> Option<[DVec2; 2]> {
fn bounding_box(&self, transform: glam::DAffine2, _render_data: &RenderData) -> Option<[DVec2; 2]> {
let mut subpath = self.shape.clone();
if transform.matrix2 == DMat2::ZERO {
return None;
@@ -68,7 +67,7 @@ impl LayerData for ShapeLayer {
subpath.bounding_box()
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _font_cache: &FontCache) {
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());

View File

@@ -35,16 +35,16 @@ pub enum ViewMode {
/// Contains metadata for rendering the document as an svg
#[derive(Debug, Clone, Copy)]
pub struct RenderData<'a> {
pub view_mode: ViewMode,
pub font_cache: &'a FontCache,
pub view_mode: ViewMode,
pub culling_bounds: Option<[DVec2; 2]>,
}
impl<'a> RenderData<'a> {
pub fn new(view_mode: ViewMode, font_cache: &'a FontCache, culling_bounds: Option<[DVec2; 2]>) -> Self {
pub fn new(font_cache: &'a FontCache, view_mode: ViewMode, culling_bounds: Option<[DVec2; 2]>) -> Self {
Self {
view_mode,
font_cache,
view_mode,
culling_bounds,
}
}

View File

@@ -34,7 +34,7 @@ pub struct TextLayer {
}
impl LayerData for TextLayer {
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, render_data: RenderData) -> bool {
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();
@@ -67,7 +67,7 @@ impl LayerData for TextLayer {
font.map(|_| r#" style="font-family: local-font;""#).unwrap_or_default()
);
} else {
let buzz_face = self.load_face(render_data.font_cache);
let buzz_face = self.load_face(render_data);
let mut path = self.to_subpath(buzz_face);
@@ -89,8 +89,8 @@ impl LayerData for TextLayer {
false
}
fn bounding_box(&self, transform: glam::DAffine2, font_cache: &FontCache) -> Option<[DVec2; 2]> {
let buzz_face = Some(self.load_face(font_cache)?);
fn bounding_box(&self, transform: glam::DAffine2, render_data: &RenderData) -> Option<[DVec2; 2]> {
let buzz_face = Some(self.load_face(render_data)?);
if transform.matrix2 == DMat2::ZERO {
return None;
@@ -99,8 +99,8 @@ impl LayerData for TextLayer {
Some((transform * self.bounding_box(&self.text, buzz_face)).bounding_box())
}
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, font_cache: &FontCache) {
let buzz_face = self.load_face(font_cache);
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, render_data: &RenderData) {
let buzz_face = self.load_face(render_data);
if intersect_quad_bez_path(quad, &self.bounding_box(&self.text, buzz_face).path(), true) {
intersections.push(path.clone());
@@ -109,8 +109,8 @@ impl LayerData for TextLayer {
}
impl TextLayer {
pub fn load_face<'a>(&self, font_cache: &'a FontCache) -> Option<Face<'a>> {
font_cache.get(&self.font).map(|data| rustybuzz::Face::from_slice(data, 0).expect("Loading font failed"))
pub fn load_face<'a>(&self, render_data: &'a RenderData) -> Option<Face<'a>> {
render_data.font_cache.get(&self.font).map(|data| rustybuzz::Face::from_slice(data, 0).expect("Loading font failed"))
}
pub fn transform(&self, transforms: &[DAffine2], mode: ViewMode) -> DAffine2 {
@@ -121,7 +121,7 @@ impl TextLayer {
transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY)
}
pub fn new(text: String, style: PathStyle, size: f64, font: Font, font_cache: &FontCache) -> Self {
pub fn new(text: String, style: PathStyle, size: f64, font: Font, render_data: &RenderData) -> Self {
let mut new = Self {
text,
path_style: style,
@@ -132,7 +132,7 @@ impl TextLayer {
cached_path: None,
};
new.cached_path = Some(new.generate_path(new.load_face(font_cache)));
new.cached_path = Some(new.generate_path(new.load_face(render_data)));
new
}
@@ -150,8 +150,8 @@ impl TextLayer {
/// Converts to a [Subpath], without populating the cache.
#[inline]
pub fn to_subpath_nonmut(&self, font_cache: &FontCache) -> Subpath {
let buzz_face = self.load_face(font_cache);
pub fn to_subpath_nonmut(&self, render_data: &RenderData) -> Subpath {
let buzz_face = self.load_face(render_data);
self.cached_path
.clone()
@@ -170,8 +170,8 @@ impl TextLayer {
Quad::from_box([DVec2::ZERO, far])
}
pub fn update_text(&mut self, text: String, font_cache: &FontCache) {
let buzz_face = self.load_face(font_cache);
pub fn update_text(&mut self, text: String, render_data: &RenderData) {
let buzz_face = self.load_face(render_data);
self.text = text;
self.cached_path = Some(self.generate_path(buzz_face));

View File

@@ -45,13 +45,6 @@ pub enum Operation {
font_name: String,
font_style: String,
},
AddImage {
path: Vec<LayerId>,
insert_index: isize,
transform: [f64; 6],
mime: String,
image_data: Vec<u8>,
},
AddNodeGraphFrame {
path: Vec<LayerId>,
insert_index: isize,