mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 13:18:12 +08:00
Font selection for text layers (#585)
* Add font dropdown * Add fonts * Font tool options * Fix tests * Replace http with https * Add variant selection * Do not embed default font * Use proxied font list API * Change default font to Merriweather * Remove outdated comment * Specify font once & load font into foreignobject * Fix tests * Rename variant to font_style * Change TextAreaInput to use FieldInput (WIP, breaks functionality) * Fix textarea functionality * Fix types * Add weight name mapping * Change labeling of "Italic" * Remove commented HTML node * Rename font "name" to "font_family" and "file" "font_file" * Fix errors * Fix fmt Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
916a575446
commit
239aa03453
@@ -81,7 +81,7 @@ impl Default for GradientToolFsmState {
|
||||
|
||||
/// Computes the transform from gradient space to layer space (where gradient space is 0..1 in layer space)
|
||||
fn gradient_space_transform(path: &[LayerId], layer: &Layer, document: &DocumentMessageHandler) -> DAffine2 {
|
||||
let bounds = layer.aabounding_box_for_transform(DAffine2::IDENTITY).unwrap();
|
||||
let bounds = layer.aabounding_box_for_transform(DAffine2::IDENTITY, &document.graphene_document.font_cache).unwrap();
|
||||
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
||||
|
||||
let multiplied = document.graphene_document.multiply_transforms(path).unwrap();
|
||||
|
||||
@@ -407,7 +407,7 @@ impl Fsm for SelectToolFsmState {
|
||||
let selected = selected.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.pivot, &selected, responses, &document.graphene_document);
|
||||
|
||||
*selected.pivot = selected.calculate_pivot();
|
||||
*selected.pivot = selected.calculate_pivot(&document.graphene_document.font_cache);
|
||||
}
|
||||
|
||||
data.layers_dragging = selected;
|
||||
|
||||
@@ -3,12 +3,14 @@ use crate::document::DocumentMessageHandler;
|
||||
use crate::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::layout::widgets::{LayoutRow, NumberInput, PropertyHolder, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::layout::layout_message::LayoutTarget;
|
||||
use crate::layout::widgets::{FontInput, LayoutRow, NumberInput, PropertyHolder, Separator, SeparatorDirection, SeparatorType, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene::document::FontCache;
|
||||
use graphene::intersection::Quad;
|
||||
use graphene::layers::style::{self, Fill, Stroke};
|
||||
use graphene::Operation;
|
||||
@@ -24,11 +26,19 @@ pub struct TextTool {
|
||||
|
||||
pub struct TextOptions {
|
||||
font_size: u32,
|
||||
font_name: String,
|
||||
font_style: String,
|
||||
font_file: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for TextOptions {
|
||||
fn default() -> Self {
|
||||
Self { font_size: 14 }
|
||||
Self {
|
||||
font_size: 24,
|
||||
font_name: "Merriweather".into(),
|
||||
font_style: "Normal (400)".into(),
|
||||
font_file: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,21 +68,60 @@ pub enum TextMessage {
|
||||
#[remain::sorted]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum TextOptionsUpdate {
|
||||
Font { family: String, style: String, file: String },
|
||||
FontSize(u32),
|
||||
}
|
||||
|
||||
impl PropertyHolder for TextTool {
|
||||
fn properties(&self) -> WidgetLayout {
|
||||
WidgetLayout::new(vec![LayoutRow::Row {
|
||||
widgets: vec![WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
unit: " px".into(),
|
||||
label: "Font Size".into(),
|
||||
value: self.options.font_size as f64,
|
||||
is_integer: true,
|
||||
min: Some(1.),
|
||||
on_update: WidgetCallback::new(|number_input: &NumberInput| TextMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value as u32)).into()),
|
||||
..NumberInput::default()
|
||||
}))],
|
||||
widgets: vec![
|
||||
WidgetHolder::new(Widget::FontInput(FontInput {
|
||||
is_style_picker: false,
|
||||
font_family: self.options.font_name.clone(),
|
||||
font_style: self.options.font_style.clone(),
|
||||
on_update: WidgetCallback::new(|font_input: &FontInput| {
|
||||
TextMessage::UpdateOptions(TextOptionsUpdate::Font {
|
||||
family: font_input.font_family.clone(),
|
||||
style: font_input.font_style.clone(),
|
||||
file: font_input.font_file.clone(),
|
||||
})
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
separator_type: SeparatorType::Related,
|
||||
})),
|
||||
WidgetHolder::new(Widget::FontInput(FontInput {
|
||||
is_style_picker: true,
|
||||
font_family: self.options.font_name.clone(),
|
||||
font_style: self.options.font_style.clone(),
|
||||
on_update: WidgetCallback::new(|font_input: &FontInput| {
|
||||
TextMessage::UpdateOptions(TextOptionsUpdate::Font {
|
||||
family: font_input.font_family.clone(),
|
||||
style: font_input.font_style.clone(),
|
||||
file: font_input.font_file.clone(),
|
||||
})
|
||||
.into()
|
||||
}),
|
||||
..Default::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
direction: SeparatorDirection::Horizontal,
|
||||
separator_type: SeparatorType::Related,
|
||||
})),
|
||||
WidgetHolder::new(Widget::NumberInput(NumberInput {
|
||||
unit: " px".into(),
|
||||
label: "Size".into(),
|
||||
value: self.options.font_size as f64,
|
||||
is_integer: true,
|
||||
min: Some(1.),
|
||||
on_update: WidgetCallback::new(|number_input: &NumberInput| TextMessage::UpdateOptions(TextOptionsUpdate::FontSize(number_input.value as u32)).into()),
|
||||
..NumberInput::default()
|
||||
})),
|
||||
],
|
||||
}])
|
||||
}
|
||||
}
|
||||
@@ -91,6 +140,13 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
|
||||
|
||||
if let ToolMessage::Text(TextMessage::UpdateOptions(action)) = action {
|
||||
match action {
|
||||
TextOptionsUpdate::Font { family, style, file } => {
|
||||
self.options.font_name = family;
|
||||
self.options.font_style = style;
|
||||
self.options.font_file = Some(file);
|
||||
|
||||
self.register_properties(responses, LayoutTarget::ToolOptions);
|
||||
}
|
||||
TextOptionsUpdate::FontSize(font_size) => self.options.font_size = font_size,
|
||||
}
|
||||
return;
|
||||
@@ -155,25 +211,33 @@ fn resize_overlays(overlays: &mut Vec<Vec<LayerId>>, responses: &mut VecDeque<Me
|
||||
}
|
||||
}
|
||||
|
||||
fn update_overlays(document: &DocumentMessageHandler, data: &mut TextToolData, responses: &mut VecDeque<Message>) {
|
||||
fn update_overlays(document: &DocumentMessageHandler, data: &mut TextToolData, responses: &mut VecDeque<Message>, font_cache: &FontCache) {
|
||||
let visible_text_layers = document.selected_visible_text_layers().collect::<Vec<_>>();
|
||||
|
||||
resize_overlays(&mut data.overlays, responses, visible_text_layers.len());
|
||||
|
||||
for (layer_path, overlay_path) in visible_text_layers.into_iter().zip(&data.overlays) {
|
||||
let bounds = document
|
||||
.graphene_document
|
||||
.layer(layer_path)
|
||||
.unwrap()
|
||||
.aabounding_box_for_transform(document.graphene_document.multiply_transforms(layer_path).unwrap())
|
||||
.unwrap();
|
||||
let bounds = visible_text_layers
|
||||
.into_iter()
|
||||
.zip(&data.overlays)
|
||||
.filter_map(|(layer_path, overlay_path)| {
|
||||
document
|
||||
.graphene_document
|
||||
.layer(layer_path)
|
||||
.unwrap()
|
||||
.aabounding_box_for_transform(document.graphene_document.multiply_transforms(layer_path).unwrap(), font_cache)
|
||||
.map(|bounds| (bounds, overlay_path))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let new_len = bounds.len();
|
||||
|
||||
for (bounds, overlay_path) in bounds {
|
||||
let operation = Operation::SetLayerTransformInViewport {
|
||||
path: overlay_path.to_vec(),
|
||||
transform: transform_from_box(bounds[0], bounds[1]),
|
||||
};
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
}
|
||||
resize_overlays(&mut data.overlays, responses, new_len);
|
||||
}
|
||||
|
||||
impl Fsm for TextToolFsmState {
|
||||
@@ -196,7 +260,7 @@ impl Fsm for TextToolFsmState {
|
||||
if let ToolMessage::Text(event) = event {
|
||||
match (self, event) {
|
||||
(state, DocumentIsDirty) => {
|
||||
update_overlays(document, data, responses);
|
||||
update_overlays(document, data, responses, &document.graphene_document.font_cache);
|
||||
|
||||
state
|
||||
}
|
||||
@@ -244,6 +308,9 @@ impl Fsm for TextToolFsmState {
|
||||
else if state == TextToolFsmState::Ready {
|
||||
let transform = DAffine2::from_translation(input.mouse.position).to_cols_array();
|
||||
let font_size = tool_options.font_size;
|
||||
let font_name = tool_options.font_name.clone();
|
||||
let font_style = tool_options.font_style.clone();
|
||||
let font_file = tool_options.font_file.clone();
|
||||
data.path = document.get_path_for_new_layer();
|
||||
|
||||
responses.push_back(
|
||||
@@ -254,6 +321,9 @@ impl Fsm for TextToolFsmState {
|
||||
text: r#""#.to_string(),
|
||||
style: style::PathStyle::new(None, Fill::solid(tool_data.primary_color)),
|
||||
size: font_size as f64,
|
||||
font_name,
|
||||
font_style,
|
||||
font_file,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
@@ -329,7 +399,8 @@ impl Fsm for TextToolFsmState {
|
||||
}
|
||||
(Editing, UpdateBounds { new_text }) => {
|
||||
resize_overlays(&mut data.overlays, responses, 1);
|
||||
let mut path = document.graphene_document.layer(&data.path).unwrap().as_text().unwrap().bounding_box(&new_text).to_path(0.1);
|
||||
let text = document.graphene_document.layer(&data.path).unwrap().as_text().unwrap();
|
||||
let mut path = text.bounding_box(&new_text, text.load_face(&document.graphene_document.font_cache)).to_path(0.1);
|
||||
|
||||
fn glam_to_kurbo(transform: DAffine2) -> kurbo::Affine {
|
||||
kurbo::Affine::new(transform.to_cols_array())
|
||||
|
||||
Reference in New Issue
Block a user