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:
0HyperCube
2022-04-21 09:50:44 +01:00
committed by Keavon Chambers
co-authored by Keavon Chambers
parent 916a575446
commit 239aa03453
36 changed files with 1029 additions and 253 deletions
@@ -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;
+93 -22
View File
@@ -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())