mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 09:28:11 +08:00
Make font selection show a live preview on hover; move its code to the backend (#3487)
* Remove FontInput.svelte * Move font picking to the backend * Fix Text tool font choice style turning to "-" on font that doesn't support previous style
This commit is contained in:
@@ -19,8 +19,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::{Doc
|
||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, PTZ};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::RawBuffer;
|
||||
use crate::messages::portfolio::utility_types::PanelType;
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::portfolio::utility_types::{FontCatalog, PanelType, PersistentData};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_blend_mode, get_fill, get_opacity};
|
||||
use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;
|
||||
@@ -36,6 +35,7 @@ use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::Raster;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::Font;
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
|
||||
@@ -2171,54 +2171,28 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
|
||||
/// Loads all of the fonts in the document.
|
||||
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {
|
||||
let mut fonts = HashSet::new();
|
||||
for (_node_id, node, _) in self.document_network().recursive_nodes() {
|
||||
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>, font_catalog: &FontCatalog) {
|
||||
let mut fonts_to_load = HashSet::new();
|
||||
|
||||
for (_, node, _) in self.document_network().recursive_nodes() {
|
||||
for input in &node.inputs {
|
||||
if let Some(TaggedValue::Font(font)) = input.as_value() {
|
||||
fonts.insert(font.clone());
|
||||
fonts_to_load.insert(font.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
for font in fonts {
|
||||
responses.add_front(FrontendMessage::TriggerFontLoad { font });
|
||||
|
||||
for font in fonts_to_load {
|
||||
if let Some(style) = font_catalog.find_font_style_in_catalog(&font) {
|
||||
responses.add_front(FrontendMessage::TriggerFontDataLoad {
|
||||
font: Font::new(font.font_family, style.to_named_style()),
|
||||
url: style.url,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_document_widgets(&self, responses: &mut VecDeque<Message>, animation_is_playing: bool, time: Duration) {
|
||||
// // Document mode (dropdown menu at the left of the bar above the viewport, before the tool options)
|
||||
// let layout = Layout(vec![LayoutGroup::Row {
|
||||
// widgets: vec![
|
||||
// DropdownInput::new(
|
||||
// vec![vec![
|
||||
// MenuListEntry::new(format!("{:?}", DocumentMode::DesignMode))
|
||||
// .label(DocumentMode::DesignMode.to_string())
|
||||
// .icon(DocumentMode::DesignMode.icon_name()),
|
||||
// // TODO: See issue #330
|
||||
// MenuListEntry::new(format!("{:?}", DocumentMode::SelectMode))
|
||||
// .label(DocumentMode::SelectMode.to_string())
|
||||
// .icon(DocumentMode::SelectMode.icon_name())
|
||||
// .on_commit(|_| todo!()),
|
||||
// // TODO: See issue #331
|
||||
// MenuListEntry::new(format!("{:?}", DocumentMode::GuideMode))
|
||||
// .label(DocumentMode::GuideMode.to_string())
|
||||
// .icon(DocumentMode::GuideMode.icon_name())
|
||||
// .on_commit(|_| todo!()),
|
||||
// ]])
|
||||
// .selected_index(Some(self.document_mode as u32))
|
||||
// .draw_icon(true)
|
||||
// .interactive(false)
|
||||
// .widget_instance(),
|
||||
// Separator::new(SeparatorType::Section).widget_instance(),
|
||||
// ],
|
||||
// }]);
|
||||
// responses.add(LayoutMessage::SendLayout {
|
||||
// layout,
|
||||
// layout_target: LayoutTarget::DocumentMode,
|
||||
// });
|
||||
|
||||
// Document bar (right portion of the bar above the viewport)
|
||||
|
||||
let mut snapping_state = self.snapping_state.clone();
|
||||
let mut snapping_state2 = self.snapping_state.clone();
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use super::document_node_definitions::{NODE_OVERRIDES, NodePropertiesContext};
|
||||
use super::utility_types::FrontendGraphDataType;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
|
||||
use crate::messages::portfolio::utility_types::{FontCatalogStyle, PersistentData};
|
||||
use crate::messages::prelude::*;
|
||||
use choice::enum_choice;
|
||||
use dyn_any::DynAny;
|
||||
@@ -34,7 +35,7 @@ pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
||||
fn optionally_update_value<T>(value: impl Fn(&T) -> Option<TaggedValue> + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync {
|
||||
move |input_value: &T| match value(input_value) {
|
||||
Some(value) => NodeGraphMessage::SetInputValue { node_id, input_index, value }.into(),
|
||||
_ => Message::NoOp,
|
||||
None => Message::NoOp,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +87,7 @@ pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<Widget
|
||||
input_type,
|
||||
blank_assist,
|
||||
exposable,
|
||||
..
|
||||
} = parameter_widgets_info;
|
||||
|
||||
let Some(document_node) = document_node else {
|
||||
@@ -759,36 +761,141 @@ pub fn array_of_vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, text_p
|
||||
}
|
||||
|
||||
pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetInstance>, Option<Vec<WidgetInstance>>) {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
let ParameterWidgetsInfo {
|
||||
persistent_data,
|
||||
document_node,
|
||||
node_id,
|
||||
index,
|
||||
..
|
||||
} = parameter_widgets_info;
|
||||
|
||||
let mut first_widgets = start_widgets(parameter_widgets_info);
|
||||
let mut second_widgets = None;
|
||||
|
||||
let from_font_input = |font: &FontInput| TaggedValue::Font(Font::new(font.font_family.clone(), font.font_style.clone()));
|
||||
|
||||
let Some(document_node) = document_node else { return (Vec::new(), None) };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return (vec![], None);
|
||||
};
|
||||
|
||||
if let Some(TaggedValue::Font(font)) = &input.as_non_exposed_value() {
|
||||
first_widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||
FontInput::new(font.font_family.clone(), font.font_style.clone())
|
||||
.on_update(update_value(from_font_input, node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_instance(),
|
||||
DropdownInput::new(vec![
|
||||
persistent_data
|
||||
.font_catalog
|
||||
.0
|
||||
.iter()
|
||||
.map(|family| {
|
||||
MenuListEntry::new(family.name.clone())
|
||||
.label(family.name.clone())
|
||||
.font(family.closest_style(400, false).preview_url(&family.name))
|
||||
.on_update({
|
||||
// Construct the new font using the new family and the initial or previous style, although this style might not exist in the catalog
|
||||
let mut new_font = Font::new(family.name.clone(), font.font_style_to_restore.clone().unwrap_or_else(|| font.font_style.clone()));
|
||||
new_font.font_style_to_restore = font.font_style_to_restore.clone();
|
||||
|
||||
// If not already, store the initial style so it can be restored if the user switches to another family
|
||||
if new_font.font_style_to_restore.is_none() {
|
||||
new_font.font_style_to_restore = Some(new_font.font_style.clone());
|
||||
}
|
||||
|
||||
// Use the closest style available in the family for the new font to ensure the style exists
|
||||
let FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&new_font.font_style, "");
|
||||
new_font.font_style = family.closest_style(weight, italic).to_named_style();
|
||||
|
||||
move |_| {
|
||||
let new_font = new_font.clone();
|
||||
|
||||
Message::Batched {
|
||||
messages: Box::new([
|
||||
PortfolioMessage::LoadFontData { font: new_font.clone() }.into(),
|
||||
update_value(move |_| TaggedValue::Font(new_font.clone()), node_id, index)(&()),
|
||||
]),
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_commit({
|
||||
// Use the new value from the user selection
|
||||
let font_family = family.name.clone();
|
||||
|
||||
// Use the previous style selection and extract its weight and italic properties, then find the closest style in the new family
|
||||
let FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&font.font_style, "");
|
||||
let font_style = family.closest_style(weight, italic).to_named_style();
|
||||
|
||||
move |_| {
|
||||
let new_font = Font::new(font_family.clone(), font_style.clone());
|
||||
|
||||
DeferMessage::AfterGraphRun {
|
||||
messages: vec![update_value(move |_| TaggedValue::Font(new_font.clone()), node_id, index)(&()), commit_value(&())],
|
||||
}
|
||||
.into()
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
])
|
||||
.selected_index(persistent_data.font_catalog.0.iter().position(|family| family.name == font.font_family).map(|i| i as u32))
|
||||
.virtual_scrolling(true)
|
||||
.widget_instance(),
|
||||
]);
|
||||
|
||||
let mut second_row = vec![TextLabel::new("").widget_instance()];
|
||||
add_blank_assist(&mut second_row);
|
||||
second_row.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_instance(),
|
||||
FontInput::new(font.font_family.clone(), font.font_style.clone())
|
||||
.is_style_picker(true)
|
||||
.on_update(update_value(from_font_input, node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_instance(),
|
||||
DropdownInput::new({
|
||||
persistent_data
|
||||
.font_catalog
|
||||
.0
|
||||
.iter()
|
||||
.find(|family| family.name == font.font_family)
|
||||
.map(|family| {
|
||||
let build_entry = |style: &FontCatalogStyle| {
|
||||
let font_style = style.to_named_style();
|
||||
MenuListEntry::new(font_style.clone())
|
||||
.label(font_style.clone())
|
||||
.on_update({
|
||||
let font_family = font.font_family.clone();
|
||||
let font_style = font_style.clone();
|
||||
|
||||
move |_| {
|
||||
// Keep the existing family
|
||||
let new_font = Font::new(font_family.clone(), font_style.clone());
|
||||
|
||||
Message::Batched {
|
||||
messages: Box::new([
|
||||
PortfolioMessage::LoadFontData { font: new_font.clone() }.into(),
|
||||
update_value(move |_| TaggedValue::Font(new_font.clone()), node_id, index)(&()),
|
||||
]),
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_commit(commit_value)
|
||||
};
|
||||
|
||||
vec![
|
||||
family.styles.iter().filter(|style| !style.italic).map(build_entry).collect::<Vec<_>>(),
|
||||
family.styles.iter().filter(|style| style.italic).map(build_entry).collect::<Vec<_>>(),
|
||||
]
|
||||
})
|
||||
.filter(|styles| !styles.is_empty())
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.selected_index(
|
||||
persistent_data
|
||||
.font_catalog
|
||||
.0
|
||||
.iter()
|
||||
.find(|family| family.name == font.font_family)
|
||||
.and_then(|family| {
|
||||
let not_italic = family.styles.iter().filter(|style| !style.italic);
|
||||
let italic = family.styles.iter().filter(|style| style.italic);
|
||||
not_italic.chain(italic).position(|style| style.to_named_style() == font.font_style)
|
||||
})
|
||||
.map(|i| i as u32),
|
||||
)
|
||||
.widget_instance(),
|
||||
]);
|
||||
second_widgets = Some(second_row);
|
||||
}
|
||||
@@ -2033,6 +2140,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
||||
}
|
||||
|
||||
pub struct ParameterWidgetsInfo<'a> {
|
||||
persistent_data: &'a PersistentData,
|
||||
document_node: Option<&'a DocumentNode>,
|
||||
node_id: NodeId,
|
||||
index: usize,
|
||||
@@ -2053,6 +2161,7 @@ impl<'a> ParameterWidgetsInfo<'a> {
|
||||
let document_node = context.network_interface.document_node(&node_id, context.selection_network_path);
|
||||
|
||||
ParameterWidgetsInfo {
|
||||
persistent_data: context.persistent_data,
|
||||
document_node,
|
||||
node_id,
|
||||
index,
|
||||
@@ -2227,7 +2336,7 @@ pub mod choice {
|
||||
|
||||
let mut row = LayoutGroup::Row { widgets };
|
||||
if let Some(desc) = self.widget_factory.description() {
|
||||
row = row.with_tooltip_label(desc);
|
||||
row = row.with_tooltip_description(desc);
|
||||
}
|
||||
row
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ pub static GLOBAL_FONT_CACHE: LazyLock<FontCache> = LazyLock::new(|| {
|
||||
// Initialize with the hardcoded font used by overlay text
|
||||
const FONT_DATA: &[u8] = include_bytes!("source-sans-pro-regular.ttf");
|
||||
let font = Font::new("Source Sans Pro".to_string(), "Regular".to_string());
|
||||
font_cache.insert(font, String::new(), FONT_DATA.to_vec());
|
||||
font_cache.insert(font, FONT_DATA.to_vec());
|
||||
font_cache
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use super::utility_types::PanelType;
|
||||
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||
use crate::messages::portfolio::utility_types::FontCatalog;
|
||||
use crate::messages::prelude::*;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::raster::Image;
|
||||
@@ -46,19 +47,21 @@ pub enum PortfolioMessage {
|
||||
},
|
||||
DestroyAllDocuments,
|
||||
EditorPreferences,
|
||||
FontCatalogLoaded {
|
||||
catalog: FontCatalog,
|
||||
},
|
||||
LoadFontData {
|
||||
font: Font,
|
||||
},
|
||||
FontLoaded {
|
||||
font_family: String,
|
||||
font_style: String,
|
||||
preview_url: String,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
Import,
|
||||
LoadDocumentResources {
|
||||
document_id: DocumentId,
|
||||
},
|
||||
LoadFont {
|
||||
font: Font,
|
||||
},
|
||||
NewDocumentWithName {
|
||||
name: String,
|
||||
},
|
||||
|
||||
@@ -349,16 +349,31 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
self.active_document_id = None;
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
}
|
||||
PortfolioMessage::FontLoaded {
|
||||
font_family,
|
||||
font_style,
|
||||
preview_url,
|
||||
data,
|
||||
} => {
|
||||
let font = Font::new(font_family, font_style);
|
||||
PortfolioMessage::FontCatalogLoaded { catalog } => {
|
||||
self.persistent_data.font_catalog = catalog;
|
||||
|
||||
self.persistent_data.font_cache.insert(font, preview_url, data);
|
||||
if let Some(document_id) = self.active_document_id {
|
||||
responses.add(PortfolioMessage::LoadDocumentResources { document_id });
|
||||
}
|
||||
|
||||
// Load the default font
|
||||
let font = Font::new(graphene_std::consts::DEFAULT_FONT_FAMILY.into(), graphene_std::consts::DEFAULT_FONT_STYLE.into());
|
||||
responses.add(PortfolioMessage::LoadFontData { font });
|
||||
}
|
||||
PortfolioMessage::LoadFontData { font } => {
|
||||
if let Some(style) = self.persistent_data.font_catalog.find_font_style_in_catalog(&font) {
|
||||
let font = Font::new(font.font_family, style.to_named_style());
|
||||
|
||||
if !self.persistent_data.font_cache.loaded_font(&font) {
|
||||
responses.add(FrontendMessage::TriggerFontDataLoad { font, url: style.url });
|
||||
}
|
||||
}
|
||||
}
|
||||
PortfolioMessage::FontLoaded { font_family, font_style, data } => {
|
||||
let font = Font::new(font_family, font_style);
|
||||
self.persistent_data.font_cache.insert(font, data);
|
||||
self.executor.update_font_cache(self.persistent_data.font_cache.clone());
|
||||
|
||||
for document_id in self.document_ids.iter() {
|
||||
let node_to_inspect = self.node_to_inspect();
|
||||
|
||||
@@ -382,6 +397,10 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
if self.active_document_mut().is_some() {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
if current_tool == &ToolType::Text {
|
||||
responses.add(TextToolMessage::RefreshEditingFontData);
|
||||
}
|
||||
}
|
||||
PortfolioMessage::EditorPreferences => self.executor.update_editor_preferences(preferences.editor_preferences()),
|
||||
PortfolioMessage::Import => {
|
||||
@@ -389,13 +408,14 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
responses.add(FrontendMessage::TriggerImport);
|
||||
}
|
||||
PortfolioMessage::LoadDocumentResources { document_id } => {
|
||||
if let Some(document) = self.document_mut(document_id) {
|
||||
document.load_layer_resources(responses);
|
||||
let catalog = &self.persistent_data.font_catalog;
|
||||
|
||||
if catalog.0.is_empty() {
|
||||
log::error!("Tried to load document resources before font catalog was loaded");
|
||||
}
|
||||
}
|
||||
PortfolioMessage::LoadFont { font } => {
|
||||
if !self.persistent_data.font_cache.loaded_font(&font) {
|
||||
responses.add_front(FrontendMessage::TriggerFontLoad { font });
|
||||
|
||||
if let Some(document) = self.documents.get_mut(&document_id) {
|
||||
document.load_layer_resources(responses, catalog);
|
||||
}
|
||||
}
|
||||
PortfolioMessage::NewDocumentWithName { name } => {
|
||||
@@ -592,7 +612,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
added_nodes = true;
|
||||
}
|
||||
|
||||
document.load_layer_resources(responses);
|
||||
document.load_layer_resources(responses, &self.persistent_data.font_catalog);
|
||||
let new_ids: HashMap<_, _> = entry.nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect();
|
||||
let layer = LayerNodeIdentifier::new_unchecked(new_ids[&NodeId(0)]);
|
||||
all_new_ids.extend(new_ids.values().cloned());
|
||||
@@ -973,7 +993,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
||||
};
|
||||
if !document.is_loaded {
|
||||
document.is_loaded = true;
|
||||
responses.add(PortfolioMessage::LoadDocumentResources { document_id });
|
||||
if self.persistent_data.font_catalog.0.is_empty() {
|
||||
responses.add_front(FrontendMessage::TriggerFontCatalogLoad);
|
||||
}
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
responses.add(PropertiesPanelMessage::Clear);
|
||||
}
|
||||
@@ -1229,10 +1251,6 @@ impl PortfolioMessageHandler {
|
||||
if self.active_document().is_some() {
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
responses.add(ToolMessage::DeactivateTools);
|
||||
} else {
|
||||
// Load the default font upon creating the first document
|
||||
let font = Font::new(graphene_std::consts::DEFAULT_FONT_FAMILY.into(), graphene_std::consts::DEFAULT_FONT_STYLE.into());
|
||||
responses.add(FrontendMessage::TriggerFontLoad { font });
|
||||
}
|
||||
|
||||
// TODO: Remove this and find a way to fix the issue where creating a new document when the node graph is open causes the transform in the new document to be incorrect
|
||||
|
||||
@@ -1,11 +1,87 @@
|
||||
use graphene_std::text::FontCache;
|
||||
use graphene_std::text::{Font, FontCache};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PersistentData {
|
||||
pub font_cache: FontCache,
|
||||
pub font_catalog: FontCatalog,
|
||||
pub use_vello: bool,
|
||||
}
|
||||
|
||||
// TODO: Should this be a BTreeMap instead?
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FontCatalog(pub Vec<FontCatalogFamily>);
|
||||
|
||||
impl FontCatalog {
|
||||
pub fn find_font_style_in_catalog(&self, font: &Font) -> Option<FontCatalogStyle> {
|
||||
let family = self.0.iter().find(|family| family.name == font.font_family);
|
||||
|
||||
let found_style = family.map(|family| {
|
||||
let FontCatalogStyle { weight, italic, .. } = FontCatalogStyle::from_named_style(&font.font_style, "");
|
||||
family.closest_style(weight, italic).clone()
|
||||
});
|
||||
|
||||
if found_style.is_none() {
|
||||
log::warn!("Font not found in catalog: {:?}", font);
|
||||
}
|
||||
|
||||
found_style
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FontCatalogFamily {
|
||||
/// The font family name.
|
||||
pub name: String,
|
||||
/// The font styles (variants) available for the font family.
|
||||
pub styles: Vec<FontCatalogStyle>,
|
||||
}
|
||||
|
||||
impl FontCatalogFamily {
|
||||
/// Finds the closest style to the given weight and italic setting.
|
||||
/// Aims to find the nearest weight while maintaining the italic setting if possible, but italic may change if no other option is available.
|
||||
pub fn closest_style(&self, weight: u32, italic: bool) -> &FontCatalogStyle {
|
||||
self.styles
|
||||
.iter()
|
||||
.map(|style| ((style.weight as i32 - weight as i32).unsigned_abs() + 10000 * (style.italic != italic) as u32, style))
|
||||
.min_by_key(|(distance, _)| *distance)
|
||||
.map(|(_, style)| style)
|
||||
.unwrap_or(&self.styles[0])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FontCatalogStyle {
|
||||
pub weight: u32,
|
||||
pub italic: bool,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
impl FontCatalogStyle {
|
||||
pub fn to_named_style(&self) -> String {
|
||||
let weight = self.weight;
|
||||
let italic = self.italic;
|
||||
|
||||
let named_weight = Font::named_weight(weight);
|
||||
let maybe_italic = if italic { " Italic" } else { "" };
|
||||
|
||||
format!("{named_weight}{maybe_italic} ({weight})")
|
||||
}
|
||||
|
||||
pub fn from_named_style(named_style: &str, url: impl Into<String>) -> FontCatalogStyle {
|
||||
let weight = named_style.split_terminator(['(', ')']).next_back().and_then(|x| x.parse::<u32>().ok()).unwrap_or(400);
|
||||
let italic = named_style.contains("Italic (");
|
||||
FontCatalogStyle { weight, italic, url: url.into() }
|
||||
}
|
||||
|
||||
/// Get the URL for the stylesheet for loading a font preview for this style of the given family name, subsetted to only the letters in the family name.
|
||||
pub fn preview_url(&self, family: impl Into<String>) -> String {
|
||||
let name = family.into().replace(' ', "+");
|
||||
let italic = if self.italic { "ital," } else { "" };
|
||||
let weight = self.weight;
|
||||
format!("https://fonts.googleapis.com/css2?display=swap&family={name}:{italic}wght@{weight}&text={name}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Platform {
|
||||
#[default]
|
||||
|
||||
Reference in New Issue
Block a user