diff --git a/.typos.toml b/.typos.toml index 16659f4c7..6c5f1564a 100644 --- a/.typos.toml +++ b/.typos.toml @@ -9,6 +9,7 @@ isse = "isse" # part of @IsseW username tye = "tye" # part of @tye-exe username ro = "ro" # read-only, also part of the username @Phen-Ro typ = "typ" # Often used because `type` is a keyword in Rust +wdth = "wdth" # The `wdth` tag is used in variable fonts # I mistype these so often tesalator = "tessellator" diff --git a/Cargo.lock b/Cargo.lock index eb9a385b0..62fd9b36c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1567,6 +1567,7 @@ dependencies = [ "ecolor", "emath", "epaint_default_fonts", + "font-types", "log", "mimalloc", "nohash-hasher", @@ -1577,6 +1578,7 @@ dependencies = [ "serde", "similar-asserts", "skrifa", + "smallvec", "vello_cpu", ] @@ -1741,6 +1743,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e4d2d0cf79d38430cc9dc9aadec84774bff2e1ba30ae2bf6c16cfce9385a23" dependencies = [ "bytemuck", + "serde", ] [[package]] @@ -4136,6 +4139,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "smithay-client-toolkit" diff --git a/Cargo.toml b/Cargo.toml index aaf9b9398..b10a4fd44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,7 @@ document-features = "0.2.11" ehttp = { version = "0.6.0", default-features = false } enum-map = "2.7.3" env_logger = { version = "0.11.8", default-features = false } +font-types = { version = "0.11.0", default-features = false, features = ["std"] } glow = "0.16.0" glutin = { version = "0.32.3", default-features = false } glutin-winit = { version = "0.5.0", default-features = false } diff --git a/crates/egui/src/context.rs b/crates/egui/src/context.rs index 2665d5edd..55e3d75e1 100644 --- a/crates/egui/src/context.rs +++ b/crates/egui/src/context.rs @@ -3262,7 +3262,7 @@ impl Context { for (name, data) in &mut font_definitions.font_data { ui.collapsing(name, |ui| { - let mut tweak = data.tweak; + let mut tweak = data.tweak.clone(); if tweak.ui(ui).changed() { Arc::make_mut(data).tweak = tweak; changed = true; diff --git a/crates/egui/src/style.rs b/crates/egui/src/style.rs index a555b9ace..56a347f0d 100644 --- a/crates/egui/src/style.rs +++ b/crates/egui/src/style.rs @@ -1,7 +1,11 @@ //! egui theme (spacing, colors, etc). use emath::Align; -use epaint::{AlphaFromCoverage, CornerRadius, Shadow, Stroke, TextOptions, text::FontTweak}; +use epaint::{ + AlphaFromCoverage, CornerRadius, Shadow, Stroke, TextOptions, + mutex::Mutex, + text::{FontTweak, Tag}, +}; use std::{collections::BTreeMap, ops::RangeInclusive, sync::Arc}; use crate::{ @@ -2837,7 +2841,7 @@ impl Widget for &mut crate::Frame { impl Widget for &mut FontTweak { fn ui(self, ui: &mut Ui) -> Response { - let original: FontTweak = *self; + let original: FontTweak = self.clone(); let mut response = Grid::new("font_tweak") .num_columns(2) @@ -2847,6 +2851,7 @@ impl Widget for &mut FontTweak { y_offset_factor, y_offset, hinting_override, + coords, } = self; ui.label("Scale"); @@ -2874,6 +2879,50 @@ impl Widget for &mut FontTweak { ui.selectable_value(hinting_override, Some(true), "Enable"); ui.selectable_value(hinting_override, Some(false), "Disable"); }); + ui.end_row(); + + ui.label("coords"); + ui.end_row(); + let mut to_remove = None; + for (i, (tag, value)) in coords.as_mut().iter_mut().enumerate() { + let tag_text = ui.ctx().data_mut(|data| { + let tag = *tag; + Arc::clone(data.get_temp_mut_or_insert_with(ui.id().with(i), move || { + Arc::new(Mutex::new(tag.to_string())) + })) + }); + + let tag_text = &mut *tag_text.lock(); + let response = ui.text_edit_singleline(tag_text); + if response.changed() + && let Ok(new_tag) = Tag::new_checked(tag_text.as_bytes()) + { + *tag = new_tag; + } + // Reset stale text when not actively editing + // (e.g. after an item was removed and indices shifted) + if !response.has_focus() + && Tag::new_checked(tag_text.as_bytes()).ok() != Some(*tag) + { + *tag_text = tag.to_string(); + } + + ui.add(DragValue::new(value)); + if ui.small_button("🗑").clicked() { + to_remove = Some(i); + } + ui.end_row(); + } + if let Some(i) = to_remove { + coords.remove(i); + } + if ui.button("Add coord").clicked() { + coords.push(b"wght", 0.0); + } + if ui.button("Clear coords").clicked() { + coords.clear(); + } + ui.end_row(); if ui.button("Reset").clicked() { *self = Default::default(); diff --git a/crates/egui/src/widget_text.rs b/crates/egui/src/widget_text.rs index 5d91f4bc1..8bc88344b 100644 --- a/crates/egui/src/widget_text.rs +++ b/crates/egui/src/widget_text.rs @@ -1,5 +1,5 @@ use emath::GuiRounding as _; -use epaint::text::TextFormat; +use epaint::text::{IntoTag, TextFormat, VariationCoords}; use std::fmt::Formatter; use std::{borrow::Cow, sync::Arc}; @@ -34,6 +34,7 @@ pub struct RichText { background_color: Color32, expand_bg: f32, text_color: Option, + coords: VariationCoords, code: bool, strong: bool, weak: bool, @@ -55,6 +56,7 @@ impl Default for RichText { background_color: Default::default(), expand_bg: 1.0, text_color: Default::default(), + coords: Default::default(), code: Default::default(), strong: Default::default(), weak: Default::default(), @@ -196,6 +198,23 @@ impl RichText { self } + /// Add a variation coordinate. + #[inline] + pub fn variation(mut self, tag: impl IntoTag, coord: f32) -> Self { + self.coords.push(tag, coord); + self + } + + /// Override the variation coordinates completely. + #[inline] + pub fn variations( + mut self, + variations: impl IntoIterator, + ) -> Self { + self.coords = VariationCoords::new(variations); + self + } + /// Override the [`TextStyle`]. #[inline] pub fn text_style(mut self, text_style: TextStyle) -> Self { @@ -391,6 +410,7 @@ impl RichText { background_color, expand_bg, text_color: _, // already used by `get_text_color` + coords, code, strong: _, // already used by `get_text_color` weak: _, // already used by `get_text_color` @@ -449,6 +469,7 @@ impl RichText { line_height, color: text_color, background: background_color, + coords, italics, underline, strikethrough, diff --git a/crates/epaint/Cargo.toml b/crates/epaint/Cargo.toml index 77facdb3f..c8a05e4d7 100644 --- a/crates/epaint/Cargo.toml +++ b/crates/epaint/Cargo.toml @@ -48,7 +48,7 @@ mint = ["emath/mint"] rayon = ["dep:rayon"] ## Allow serialization using [`serde`](https://docs.rs/serde). -serde = ["dep:serde", "ahash/serde", "emath/serde", "ecolor/serde"] +serde = ["dep:serde", "ahash/serde", "emath/serde", "ecolor/serde", "font-types/serde", "smallvec/serde"] ## Change Vertex layout to be compatible with unity unity = [] @@ -62,12 +62,14 @@ emath.workspace = true ecolor.workspace = true ahash.workspace = true +font-types.workspace = true log.workspace = true nohash-hasher.workspace = true parking_lot.workspace = true # Using parking_lot over std::sync::Mutex gives 50% speedups in some real-world scenarios. profiling.workspace = true self_cell.workspace = true skrifa.workspace = true +smallvec.workspace = true vello_cpu.workspace = true #! ### Optional dependencies diff --git a/crates/epaint/src/text/font.rs b/crates/epaint/src/text/font.rs index 150aca34a..61f9f9f2f 100644 --- a/crates/epaint/src/text/font.rs +++ b/crates/epaint/src/text/font.rs @@ -12,7 +12,7 @@ use vello_cpu::{color, kurbo}; use crate::{ TextOptions, TextureAtlas, text::{ - FontTweak, + FontTweak, VariationCoords, fonts::{Blob, CachedFamily, FontFaceKey}, }, }; @@ -145,8 +145,8 @@ struct GlyphCacheKey(u64); impl nohash_hasher::IsEnabled for GlyphCacheKey {} impl GlyphCacheKey { - fn new(glyph_id: skrifa::GlyphId, metrics: &ScaledMetrics, bin: SubpixelBin) -> Self { - let ScaledMetrics { + fn new(glyph_id: skrifa::GlyphId, metrics: &StyledMetrics, bin: SubpixelBin) -> Self { + let StyledMetrics { pixels_per_point, px_scale_factor, .. @@ -197,10 +197,10 @@ impl FontCell { fn allocate_glyph_uncached( &mut self, atlas: &mut TextureAtlas, - metrics: &ScaledMetrics, + metrics: &StyledMetrics, glyph_info: &GlyphInfo, bin: SubpixelBin, - location: &skrifa::instance::Location, + location: skrifa::instance::LocationRef<'_>, ) -> Option { let glyph_id = glyph_info.id?; @@ -337,8 +337,6 @@ pub struct FontFace { font: FontCell, tweak: FontTweak, - /// Variable font location (for weight axis, etc.) - location: skrifa::instance::Location, glyph_info_cache: ahash::HashMap, glyph_alloc_cache: ahash::HashMap, } @@ -350,7 +348,6 @@ impl FontFace { font_data: Blob, index: u32, tweak: FontTweak, - preferred_weight: Option, ) -> Result> { let font = FontCell::try_new(font_data, |font_data| { let skrifa_font = @@ -396,44 +393,10 @@ impl FontFace { }) })?; - // Use preferred_weight if provided, otherwise try to read from the OS/2 table or fvar default - let weight = preferred_weight.or_else(|| { - // First try OS/2 table - if let Some(w) = font - .borrow_dependent() - .skrifa - .os2() - .ok() - .map(|os2| os2.us_weight_class()) - { - return Some(w); - } - // If no OS/2 or preferred_weight, try to get default from variable font's fvar table - font.borrow_dependent() - .skrifa - .axes() - .iter() - .find(|axis| axis.tag() == skrifa::raw::types::Tag::new(b"wght")) - .map(|axis| axis.default_value() as u16) - }); - - // Create location for variable font with weight axis - // If weight is provided (either from preferred_weight, OS/2, or fvar default), use it - // Otherwise fall back to Location::default() which uses all axis defaults - let location = if let Some(w) = weight { - font.borrow_dependent() - .skrifa - .axes() - .location([("wght", w as f32)]) - } else { - skrifa::instance::Location::default() - }; - Ok(Self { name, font, tweak, - location, glyph_info_cache: Default::default(), glyph_alloc_cache: Default::default(), }) @@ -537,7 +500,7 @@ impl FontFace { #[inline] pub(super) fn pair_kerning_pixels( &self, - metrics: &ScaledMetrics, + metrics: &StyledMetrics, last_glyph_id: skrifa::GlyphId, glyph_id: skrifa::GlyphId, ) -> f32 { @@ -559,7 +522,7 @@ impl FontFace { #[inline] pub fn pair_kerning( &self, - metrics: &ScaledMetrics, + metrics: &StyledMetrics, last_glyph_id: skrifa::GlyphId, glyph_id: skrifa::GlyphId, ) -> f32 { @@ -567,7 +530,12 @@ impl FontFace { } #[inline(always)] - pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics { + pub fn styled_metrics( + &self, + pixels_per_point: f32, + font_size: f32, + coords: &VariationCoords, + ) -> StyledMetrics { let pt_scale_factor = self.font.px_scale_factor(font_size * self.tweak.scale); let font_data = self.font.borrow_dependent(); let ascent = (font_data.metrics.ascent * pt_scale_factor).round_ui(); @@ -581,20 +549,32 @@ impl FontFace { + self.tweak.y_offset) .round_ui(); - ScaledMetrics { + let axes = font_data.skrifa.axes(); + // Override the default coordinates with ones specified via FontTweak, then the ones specified directly via the + // argument (probably from TextFormat). + let settings = self + .tweak + .coords + .as_ref() + .iter() + .chain(coords.as_ref().iter()); + let location = axes.location(settings); + + StyledMetrics { pixels_per_point, px_scale_factor, scale, y_offset_in_points, ascent, row_height: ascent - descent + line_gap, + location, } } pub fn allocate_glyph( &mut self, atlas: &mut TextureAtlas, - metrics: &ScaledMetrics, + metrics: &StyledMetrics, glyph_info: GlyphInfo, chr: char, h_pos: f32, @@ -628,7 +608,7 @@ impl FontFace { let allocation = self .font - .allocate_glyph_uncached(atlas, metrics, &glyph_info, bin, &self.location) + .allocate_glyph_uncached(atlas, metrics, &glyph_info, bin, (&metrics.location).into()) .unwrap_or_default(); entry.insert(allocation); @@ -665,12 +645,17 @@ impl Font<'_> { }) } - pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics { + pub fn styled_metrics( + &self, + pixels_per_point: f32, + font_size: f32, + coords: &VariationCoords, + ) -> StyledMetrics { self.cached_family .fonts .first() .and_then(|key| self.fonts_by_id.get(key)) - .map(|font_face| font_face.scaled_metrics(pixels_per_point, font_size)) + .map(|font_face| font_face.styled_metrics(pixels_per_point, font_size, coords)) .unwrap_or_default() } @@ -713,8 +698,8 @@ impl Font<'_> { } /// Metrics for a font at a specific screen-space scale. -#[derive(Clone, Copy, Debug, PartialEq, Default)] -pub struct ScaledMetrics { +#[derive(Clone, Debug, PartialEq, Default)] +pub struct StyledMetrics { /// The DPI part of the screen-space scale. pub pixels_per_point: f32, @@ -738,6 +723,9 @@ pub struct ScaledMetrics { /// /// Returns a value rounded to [`emath::GUI_ROUNDING`]. pub row_height: f32, + + /// Resolved variation coordinates. + pub location: skrifa::instance::Location, } /// Code points that will always be invisible (zero width). diff --git a/crates/epaint/src/text/fonts.rs b/crates/epaint/src/text/fonts.rs index 19876b571..5099e0085 100644 --- a/crates/epaint/src/text/fonts.rs +++ b/crates/epaint/src/text/fonts.rs @@ -10,7 +10,7 @@ use std::{ use crate::{ TextureAtlas, text::{ - Galley, LayoutJob, LayoutSection, TextOptions, + Galley, LayoutJob, LayoutSection, TextOptions, VariationCoords, font::{Font, FontFace, GlyphInfo}, }, }; @@ -125,12 +125,6 @@ pub struct FontData { /// Extra scale and vertical tweak to apply to all text of this font. pub tweak: FontTweak, - - /// The font weight (100-900), if available. - /// Standard values: 100 (Thin), 200 (Extra Light), 300 (Light), 400 (Regular), - /// 500 (Medium), 600 (Semi Bold), 700 (Bold), 800 (Extra Bold), 900 (Black). - /// `None` if the weight could not be determined. - pub weight: Option, } impl FontData { @@ -139,7 +133,6 @@ impl FontData { font: Cow::Borrowed(font), index: 0, tweak: Default::default(), - weight: None, } } @@ -148,43 +141,12 @@ impl FontData { font: Cow::Owned(font), index: 0, tweak: Default::default(), - weight: None, } } pub fn tweak(self, tweak: FontTweak) -> Self { Self { tweak, ..self } } - - /// Set the font weight (100-900). - /// - /// This is typically read automatically from the font file when loaded, - /// but can be overridden manually if needed. - /// - /// Standard weight values: - /// - 100: Thin - /// - 200: Extra Light - /// - 300: Light - /// - 400: Regular/Normal - /// - 500: Medium - /// - 600: Semi Bold - /// - 700: Bold - /// - 800: Extra Bold - /// - 900: Black - /// - /// # Example - /// ``` - /// # use epaint::text::FontData; - /// let font_data = FontData::from_static(include_bytes!("../../../epaint_default_fonts/fonts/Ubuntu-Light.ttf")) - /// .weight(300); // Override to Light weight - /// assert_eq!(font_data.weight, Some(300)); - /// ``` - pub fn weight(self, weight: u16) -> Self { - Self { - weight: Some(weight), - ..self - } - } } impl AsRef<[u8]> for FontData { @@ -196,7 +158,7 @@ impl AsRef<[u8]> for FontData { // ---------------------------------------------------------------------------- /// Extra scale and vertical tweak to apply to all text of a certain font. -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct FontTweak { /// Scale the font's glyphs by this much. @@ -228,6 +190,9 @@ pub struct FontTweak { /// /// `None` means use the global setting. pub hinting_override: Option, + + /// Override the font's default variation coordinates. + pub coords: VariationCoords, } impl Default for FontTweak { @@ -237,6 +202,7 @@ impl Default for FontTweak { y_offset_factor: 0.0, y_offset: 0.0, hinting_override: None, + coords: VariationCoords::default(), } } } @@ -701,7 +667,12 @@ impl FontsView<'_> { pub fn row_height(&mut self, font_id: &FontId) -> f32 { self.fonts .font(&font_id.family) - .scaled_metrics(self.pixels_per_point, font_id.size) + .styled_metrics( + self.pixels_per_point, + font_id.size, + // TODO(valadaptive): use font variation coords when calculating row height + &VariationCoords::default(), + ) .row_height } @@ -807,15 +778,13 @@ impl FontsImpl { let mut fonts_by_id: nohash_hasher::IntMap = Default::default(); let mut fonts_by_name: ahash::HashMap = Default::default(); for (name, font_data) in &definitions.font_data { - let tweak = font_data.tweak; let blob = blob_from_font_data(font_data); let font_face = FontFace::new( options, name.clone(), blob, font_data.index, - tweak, - font_data.weight, + font_data.tweak.clone(), ) .unwrap_or_else(|err| panic!("Error parsing {name:?} TTF/OTF font file: {err}")); let key = FontFaceKey::new(); diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 9aeeff137..9b53e3301 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -8,7 +8,7 @@ use crate::{ Color32, Mesh, Stroke, Vertex, stroke::PathStroke, text::{ - font::{ScaledMetrics, is_cjk, is_cjk_break_allowed}, + font::{StyledMetrics, is_cjk, is_cjk_break_allowed}, fonts::FontFaceKey, }, }; @@ -160,7 +160,7 @@ fn layout_section( } = section; let mut font = fonts.font(&format.font_id.family); let font_size = format.font_id.size; - let font_metrics = font.scaled_metrics(pixels_per_point, font_size); + let font_metrics = font.styled_metrics(pixels_per_point, font_size, &format.coords); let line_height = section .format .line_height @@ -178,7 +178,7 @@ fn layout_section( // Optimization: only recompute `ScaledMetrics` when the concrete `FontImpl` changes. let mut current_font = FontFaceKey::INVALID; - let mut current_font_face_metrics = ScaledMetrics::default(); + let mut current_font_face_metrics = StyledMetrics::default(); for chr in job.text[byte_range.clone()].chars() { if job.break_on_newline && chr == '\n' { @@ -192,7 +192,9 @@ fn layout_section( current_font = font_id; current_font_face_metrics = font_face .as_ref() - .map(|font_face| font_face.scaled_metrics(pixels_per_point, font_size)) + .map(|font_face| { + font_face.styled_metrics(pixels_per_point, font_size, &format.coords) + }) .unwrap_or_default(); } @@ -468,7 +470,7 @@ fn replace_last_glyph_with_overflow_character( let mut font_face = font.fonts_by_id.get_mut(&font_id); let font_face_metrics = font_face .as_mut() - .map(|f| f.scaled_metrics(pixels_per_point, font_size)) + .map(|f| f.styled_metrics(pixels_per_point, font_size, §ion.format.coords)) .unwrap_or_default(); let overflow_glyph_x = if let Some(prev_glyph) = row.glyphs.last() { @@ -519,7 +521,8 @@ fn replace_last_glyph_with_overflow_character( }) .unwrap_or_default(); - let font_metrics = font.scaled_metrics(pixels_per_point, font_size); + let font_metrics = + font.styled_metrics(pixels_per_point, font_size, §ion.format.coords); let line_height = section .format .line_height @@ -1212,7 +1215,7 @@ mod tests { let font_id = FontId::default(); let font_height = fonts .font(&font_id.family) - .scaled_metrics(pixels_per_point, font_id.size) + .styled_metrics(pixels_per_point, font_id.size, &VariationCoords::default()) .row_height; let job = LayoutJob::simple(String::new(), font_id, Color32::WHITE, f32::INFINITY); @@ -1245,7 +1248,7 @@ mod tests { let font_id = FontId::default(); let font_height = fonts .font(&font_id.family) - .scaled_metrics(pixels_per_point, font_id.size) + .styled_metrics(pixels_per_point, font_id.size, &VariationCoords::default()) .row_height; let job = LayoutJob::simple("Hi!\n".to_owned(), font_id, Color32::WHITE, f32::INFINITY); diff --git a/crates/epaint/src/text/text_layout_types.rs b/crates/epaint/src/text/text_layout_types.rs index b5bef62d5..d887fb13a 100644 --- a/crates/epaint/src/text/text_layout_types.rs +++ b/crates/epaint/src/text/text_layout_types.rs @@ -1,5 +1,5 @@ -use std::ops::Range; use std::sync::Arc; +use std::{ops::Range, str::FromStr as _}; use super::{ cursor::{CCursor, LayoutCursor}, @@ -7,6 +7,8 @@ use super::{ }; use crate::{Color32, FontId, Mesh, Stroke, text::FontsView}; use emath::{Align, GuiRounding as _, NumExt as _, OrderedFloat, Pos2, Rect, Vec2, pos2, vec2}; +pub use font_types::Tag; +use smallvec::SmallVec; /// Describes the task of laying out text. /// @@ -257,6 +259,107 @@ impl std::hash::Hash for LayoutSection { // ---------------------------------------------------------------------------- +/// Helper trait for all types that can be parsed as a [`font_types::Tag`]. +pub trait IntoTag { + fn into_tag(self) -> font_types::Tag; +} + +impl IntoTag for font_types::Tag { + #[inline(always)] + fn into_tag(self) -> font_types::Tag { + self + } +} + +impl IntoTag for u32 { + #[inline(always)] + fn into_tag(self) -> font_types::Tag { + font_types::Tag::from_u32(self) + } +} + +impl IntoTag for [u8; 4] { + #[inline(always)] + fn into_tag(self) -> font_types::Tag { + font_types::Tag::new_checked(&self).expect("Invalid variation axis tag") + } +} + +impl IntoTag for &[u8; 4] { + #[inline(always)] + fn into_tag(self) -> font_types::Tag { + font_types::Tag::new_checked(self).expect("Invalid variation axis tag") + } +} + +impl IntoTag for &str { + #[inline(always)] + fn into_tag(self) -> font_types::Tag { + font_types::Tag::from_str(self).expect("Invalid variation axis tag") + } +} + +/// List of font variation coordinates by axis tag. If more than one coordinate for a given axis is provided, the last +/// one added is used. +#[derive(Clone, Debug, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +pub struct VariationCoords(SmallVec<[(font_types::Tag, f32); 2]>); + +impl VariationCoords { + /// Create a list of variation coordinates from a sequence of (tag, value) pairs. + /// + /// ## Example: + /// ``` + /// use epaint::text::VariationCoords; + /// + /// let coords = VariationCoords::new([ + /// (b"wght", 500.0), + /// (b"wdth", 75.0), + /// ]); + /// ``` + pub fn new(values: impl IntoIterator) -> Self { + Self(values.into_iter().map(|(t, c)| (t.into_tag(), c)).collect()) + } + + /// Add a variation coordinate to the list. + #[inline(always)] + pub fn push(&mut self, tag: impl IntoTag, coord: f32) { + self.0.push((tag.into_tag(), coord)); + } + + /// Remove the coordinate at the given index. + pub fn remove(&mut self, index: usize) { + self.0.remove(index); + } + + pub fn clear(&mut self) { + self.0.clear(); + } +} + +impl AsRef<[(font_types::Tag, f32)]> for VariationCoords { + #[inline(always)] + fn as_ref(&self) -> &[(font_types::Tag, f32)] { + &self.0 + } +} + +impl AsMut<[(font_types::Tag, f32)]> for VariationCoords { + fn as_mut(&mut self) -> &mut [(font_types::Tag, f32)] { + &mut self.0 + } +} + +impl std::hash::Hash for VariationCoords { + fn hash(&self, state: &mut H) { + self.0.len().hash(state); + for (tag, coord) in &self.0 { + tag.hash(state); + OrderedFloat(*coord).hash(state); + } + } +} + /// Formatting option for a section of text. #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -287,6 +390,8 @@ pub struct TextFormat { /// Default: 1.0 pub expand_bg: f32, + pub coords: VariationCoords, + pub italics: bool, pub underline: Stroke, @@ -315,6 +420,7 @@ impl Default for TextFormat { color: Color32::GRAY, background: Color32::TRANSPARENT, expand_bg: 1.0, + coords: VariationCoords::default(), italics: false, underline: Stroke::NONE, strikethrough: Stroke::NONE, @@ -333,6 +439,7 @@ impl std::hash::Hash for TextFormat { color, background, expand_bg, + coords, italics, underline, strikethrough, @@ -346,6 +453,7 @@ impl std::hash::Hash for TextFormat { color.hash(state); background.hash(state); emath::OrderedFloat(*expand_bg).hash(state); + coords.hash(state); italics.hash(state); underline.hash(state); strikethrough.hash(state);