mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-18 16:48:13 +08:00
Image and text bug fixes (#685)
* Image and text bugfixes * Mark only the required layer types as dirty * Fix doctest * Disable selection if empty * Cleanup naming * Simplify cache deleting on export * Minor css style change * Nit Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -2,7 +2,7 @@ use crate::message_prelude::*;
|
||||
|
||||
use graphene::color::Color;
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::style::{self, Fill, ViewMode};
|
||||
use graphene::layers::style::{self, Fill, RenderData, ViewMode};
|
||||
use graphene::layers::text_layer::FontCache;
|
||||
use graphene::DocumentResponse;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
@@ -85,9 +85,10 @@ impl MessageHandler<ArtboardMessage, &FontCache> for ArtboardMessageHandler {
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
let render_data = RenderData::new(ViewMode::Normal, font_cache, None, false);
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateDocumentArtboards {
|
||||
svg: self.artboards_graphene_document.render_root(ViewMode::Normal, font_cache, None),
|
||||
svg: self.artboards_graphene_document.render_root(render_data),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::clipboards::Clipboard;
|
||||
use super::layer_panel::{layer_panel_entry, LayerDataTypeDiscriminant, LayerMetadata, LayerPanelEntry, RawBuffer};
|
||||
use super::layer_panel::{layer_panel_entry, LayerMetadata, LayerPanelEntry, RawBuffer};
|
||||
use super::properties_panel_message_handler::PropertiesPanelMessageHandlerData;
|
||||
use super::utility_types::{AlignAggregate, AlignAxis, DocumentSave, FlipAxis};
|
||||
use super::utility_types::{DocumentMode, TargetDocument};
|
||||
@@ -21,16 +21,15 @@ use graphene::color::Color;
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::blend_mode::BlendMode;
|
||||
use graphene::layers::folder_layer::FolderLayer;
|
||||
use graphene::layers::layer_info::LayerDataType;
|
||||
use graphene::layers::style::{Fill, ViewMode};
|
||||
use graphene::layers::text_layer::FontCache;
|
||||
use graphene::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant};
|
||||
use graphene::layers::style::{Fill, RenderData, ViewMode};
|
||||
use graphene::layers::text_layer::{Font, FontCache};
|
||||
use graphene::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use log::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DocumentMessageHandler {
|
||||
@@ -491,18 +490,20 @@ impl DocumentMessageHandler {
|
||||
path
|
||||
}
|
||||
|
||||
/// Creates the blob URLs for the image data in the document
|
||||
pub fn load_image_data(&self, responses: &mut VecDeque<Message>, root: &LayerDataType, mut path: Vec<LayerId>) {
|
||||
let mut image_data = Vec::new();
|
||||
fn walk_layers(data: &LayerDataType, path: &mut Vec<LayerId>, image_data: &mut Vec<FrontendImageData>) {
|
||||
/// Loads layer resources such as creating the blob URLs for the images and loading all of the fonts in the document
|
||||
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>, root: &LayerDataType, mut path: Vec<LayerId>) {
|
||||
fn walk_layers(data: &LayerDataType, path: &mut Vec<LayerId>, image_data: &mut Vec<FrontendImageData>, fonts: &mut HashSet<Font>) {
|
||||
match data {
|
||||
LayerDataType::Folder(f) => {
|
||||
for (id, layer) in f.layer_ids.iter().zip(f.layers().iter()) {
|
||||
path.push(*id);
|
||||
walk_layers(&layer.data, path, image_data);
|
||||
walk_layers(&layer.data, path, image_data, fonts);
|
||||
path.pop();
|
||||
}
|
||||
}
|
||||
LayerDataType::Text(txt) => {
|
||||
fonts.insert(txt.font.clone());
|
||||
}
|
||||
LayerDataType::Image(img) => image_data.push(FrontendImageData {
|
||||
path: path.clone(),
|
||||
image_data: img.image_data.clone(),
|
||||
@@ -512,10 +513,15 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
walk_layers(root, &mut path, &mut image_data);
|
||||
let mut image_data = Vec::new();
|
||||
let mut fonts = HashSet::new();
|
||||
walk_layers(root, &mut path, &mut image_data, &mut fonts);
|
||||
if !image_data.is_empty() {
|
||||
responses.push_front(FrontendMessage::UpdateImageData { image_data }.into());
|
||||
}
|
||||
for font in fonts {
|
||||
responses.push_front(FrontendMessage::TriggerFontLoad { font, is_default: false }.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>) {
|
||||
@@ -1040,12 +1046,14 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
|
||||
let old_transform = self.graphene_document.root.transform;
|
||||
// Reset the root's transform (required to avoid any rotation by the user)
|
||||
self.graphene_document.root.transform = DAffine2::IDENTITY;
|
||||
self.graphene_document.root.cache_dirty = true;
|
||||
GrapheneDocument::mark_children_as_dirty(&mut self.graphene_document.root);
|
||||
|
||||
// Calculates the bounding box of the region to be exported
|
||||
use crate::frontend::utility_types::ExportBounds;
|
||||
let bbox = match bounds {
|
||||
crate::frontend::utility_types::ExportBounds::AllArtwork => self.all_layer_bounds(font_cache),
|
||||
crate::frontend::utility_types::ExportBounds::Artboard(id) => self
|
||||
ExportBounds::AllArtwork => self.all_layer_bounds(font_cache),
|
||||
ExportBounds::Selection => self.selected_visible_layers_bounding_box(font_cache),
|
||||
ExportBounds::Artboard(id) => self
|
||||
.artboard_message_handler
|
||||
.artboards_graphene_document
|
||||
.layer(&[id])
|
||||
@@ -1061,14 +1069,15 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
|
||||
false => file_name + file_suffix,
|
||||
};
|
||||
|
||||
let rendered = self.graphene_document.render_root(self.view_mode, font_cache, None);
|
||||
let render_data = RenderData::new(self.view_mode, font_cache, None, true);
|
||||
let rendered = self.graphene_document.render_root(render_data);
|
||||
let document = format!(
|
||||
r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="{} {} {} {}" width="{}px" height="{}">{}{}</svg>"#,
|
||||
bbox[0].x, bbox[0].y, size.x, size.y, size.x, size.y, "\n", rendered
|
||||
);
|
||||
|
||||
self.graphene_document.root.transform = old_transform;
|
||||
self.graphene_document.root.cache_dirty = true;
|
||||
GrapheneDocument::mark_children_as_dirty(&mut self.graphene_document.root);
|
||||
|
||||
if file_type == FileType::Svg {
|
||||
responses.push_back(FrontendMessage::TriggerFileDownload { document, name }.into());
|
||||
@@ -1218,9 +1227,10 @@ impl MessageHandler<DocumentMessage, (&InputPreprocessorMessageHandler, &FontCac
|
||||
}
|
||||
RenameLayer { layer_path, new_name } => responses.push_back(DocumentOperation::RenameLayer { layer_path, new_name }.into()),
|
||||
RenderDocument => {
|
||||
let render_data = RenderData::new(self.view_mode, font_cache, Some(ipp.document_bounds()), false);
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateDocumentArtwork {
|
||||
svg: self.graphene_document.render_root(self.view_mode, font_cache, Some(ipp.document_bounds())),
|
||||
svg: self.graphene_document.render_root(render_data),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use graphene::layers::layer_info::{Layer, LayerData, LayerDataType};
|
||||
use graphene::layers::style::ViewMode;
|
||||
use graphene::layers::layer_info::{Layer, LayerData, LayerDataTypeDiscriminant};
|
||||
use graphene::layers::style::{RenderData, ViewMode};
|
||||
use graphene::layers::text_layer::FontCache;
|
||||
use graphene::LayerId;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Copy)]
|
||||
pub struct LayerMetadata {
|
||||
@@ -27,7 +26,8 @@ pub fn layer_panel_entry(layer_metadata: &LayerMetadata, transform: DAffine2, la
|
||||
|
||||
let mut thumbnail = String::new();
|
||||
let mut svg_defs = String::new();
|
||||
layer.data.clone().render(&mut thumbnail, &mut svg_defs, &mut vec![transform], ViewMode::Normal, font_cache, None);
|
||||
let render_data = RenderData::new(ViewMode::Normal, font_cache, None, false);
|
||||
layer.data.clone().render(&mut thumbnail, &mut svg_defs, &mut vec![transform], render_data);
|
||||
let transform = transform.to_cols_array().iter().map(ToString::to_string).collect::<Vec<_>>().join(",");
|
||||
let thumbnail = if let [(x_min, y_min), (x_max, y_max)] = arr.as_slice() {
|
||||
format!(
|
||||
@@ -90,37 +90,3 @@ pub struct LayerPanelEntry {
|
||||
pub path: Vec<LayerId>,
|
||||
pub thumbnail: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub enum LayerDataTypeDiscriminant {
|
||||
Folder,
|
||||
Shape,
|
||||
Text,
|
||||
Image,
|
||||
}
|
||||
|
||||
impl fmt::Display for LayerDataTypeDiscriminant {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
let name = match self {
|
||||
LayerDataTypeDiscriminant::Folder => "Folder",
|
||||
LayerDataTypeDiscriminant::Shape => "Shape",
|
||||
LayerDataTypeDiscriminant::Text => "Text",
|
||||
LayerDataTypeDiscriminant::Image => "Image",
|
||||
};
|
||||
|
||||
formatter.write_str(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&LayerDataType> for LayerDataTypeDiscriminant {
|
||||
fn from(data: &LayerDataType) -> Self {
|
||||
use LayerDataType::*;
|
||||
|
||||
match data {
|
||||
Folder(_) => LayerDataTypeDiscriminant::Folder,
|
||||
Shape(_) => LayerDataTypeDiscriminant::Shape,
|
||||
Text(_) => LayerDataTypeDiscriminant::Text,
|
||||
Image(_) => LayerDataTypeDiscriminant::Image,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::style::ViewMode;
|
||||
use graphene::layers::style::{RenderData, ViewMode};
|
||||
use graphene::layers::text_layer::FontCache;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -32,7 +32,8 @@ impl MessageHandler<OverlaysMessage, (bool, &FontCache, &InputPreprocessorMessag
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateDocumentOverlays {
|
||||
svg: if overlays_visible {
|
||||
self.overlays_graphene_document.render_root(ViewMode::Normal, font_cache, Some(ipp.document_bounds()))
|
||||
let render_data = RenderData::new(ViewMode::Normal, font_cache, Some(ipp.document_bounds()), false);
|
||||
self.overlays_graphene_document.render_root(render_data)
|
||||
} else {
|
||||
String::from("")
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::layout::widgets::PropertyHolder;
|
||||
use crate::{dialog, message_prelude::*};
|
||||
|
||||
use graphene::layers::layer_info::LayerDataTypeDiscriminant;
|
||||
use graphene::layers::text_layer::{Font, FontCache};
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
@@ -47,11 +48,10 @@ impl PortfolioMessageHandler {
|
||||
// Uses binary search to find the index of the element where number is bigger than i
|
||||
let new_doc_title_num = doc_title_numbers.binary_search(&0).map_or_else(|e| e, |v| v) + 1;
|
||||
|
||||
let name = match new_doc_title_num {
|
||||
match new_doc_title_num {
|
||||
1 => DEFAULT_DOCUMENT_NAME.to_string(),
|
||||
_ => format!("{} {}", DEFAULT_DOCUMENT_NAME, new_doc_title_num),
|
||||
};
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Fix how this doesn't preserve tab order upon loading new document from *File > Load*
|
||||
@@ -81,7 +81,7 @@ impl PortfolioMessageHandler {
|
||||
);
|
||||
new_document.update_layer_tree_options_bar_widgets(responses, &self.font_cache);
|
||||
|
||||
new_document.load_image_data(responses, &new_document.graphene_document.root.data, Vec::new());
|
||||
new_document.load_layer_resources(responses, &new_document.graphene_document.root.data, Vec::new());
|
||||
|
||||
self.documents.insert(document_id, new_document);
|
||||
|
||||
@@ -287,7 +287,8 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
is_default,
|
||||
} => {
|
||||
self.font_cache.insert(Font::new(font_family, font_style), preview_url, data, is_default);
|
||||
responses.push_back(DocumentMessage::DirtyRenderDocument.into());
|
||||
self.active_document_mut().graphene_document.mark_all_layers_of_type_as_dirty(LayerDataTypeDiscriminant::Text);
|
||||
responses.push_back(DocumentMessage::RenderDocument.into());
|
||||
}
|
||||
LoadFont { font, is_default } => {
|
||||
if !self.font_cache.loaded_font(&font) {
|
||||
@@ -387,7 +388,7 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
self.active_document().load_image_data(responses, &entry.layer.data, destination_path.clone());
|
||||
self.active_document().load_layer_resources(responses, &entry.layer.data, destination_path.clone());
|
||||
responses.push_front(
|
||||
DocumentOperation::InsertLayer {
|
||||
layer: entry.layer.clone(),
|
||||
@@ -428,7 +429,7 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for Port
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
self.active_document().load_image_data(responses, &entry.layer.data, destination_path.clone());
|
||||
self.active_document().load_layer_resources(responses, &entry.layer.data, destination_path.clone());
|
||||
responses.push_front(
|
||||
DocumentOperation::InsertLayer {
|
||||
layer: entry.layer.clone(),
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use super::layer_panel::LayerDataTypeDiscriminant;
|
||||
use super::utility_types::TargetDocument;
|
||||
use crate::document::properties_panel_message::TransformOp;
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
@@ -10,7 +9,7 @@ use crate::message_prelude::*;
|
||||
|
||||
use graphene::color::Color;
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::layer_info::{Layer, LayerDataType};
|
||||
use graphene::layers::layer_info::{Layer, LayerDataType, LayerDataTypeDiscriminant};
|
||||
use graphene::layers::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke};
|
||||
use graphene::layers::text_layer::{FontCache, TextLayer};
|
||||
use graphene::{LayerId, Operation};
|
||||
|
||||
Reference in New Issue
Block a user