diff --git a/parley/src/lib.rs b/parley/src/lib.rs index bab180208..41a32c1cf 100644 --- a/parley/src/lib.rs +++ b/parley/src/lib.rs @@ -116,7 +116,6 @@ mod util; pub mod editing; pub mod layout; -pub mod setting; pub mod style; #[cfg(test)] diff --git a/parley/src/resolve/mod.rs b/parley/src/resolve/mod.rs index 6c35da4ab..61a8c720f 100644 --- a/parley/src/resolve/mod.rs +++ b/parley/src/resolve/mod.rs @@ -11,8 +11,8 @@ pub(crate) use range::RangedStyleBuilder; use alloc::{vec, vec::Vec}; use super::style::{ - Brush, FontFamily, FontFamilyName, FontFeature, FontFeatures, FontStyle, FontVariation, - FontVariations, FontWeight, FontWidth, StyleProperty, + Brush, FontFamily, FontFamilyName, FontFeature, FontStyle, FontVariation, FontWeight, + FontWidth, StyleProperty, }; use crate::font::FontContext; use crate::style::TextStyle; @@ -139,8 +139,12 @@ impl ResolveContext { StyleProperty::FontWidth(value) => FontWidth(*value), StyleProperty::FontStyle(value) => FontStyle(*value), StyleProperty::FontWeight(value) => FontWeight(*value), - StyleProperty::FontVariations(value) => FontVariations(self.resolve_variations(value)), - StyleProperty::FontFeatures(value) => FontFeatures(self.resolve_features(value)), + StyleProperty::FontVariations(value) => { + FontVariations(self.resolve_variations(value.as_ref())) + } + StyleProperty::FontFeatures(value) => { + FontFeatures(self.resolve_features(value.as_ref())) + } StyleProperty::Locale(value) => Locale( value.and_then(|v| icu_locale_core::Locale::try_from_str(v).map(|v| v.id).ok()), ), @@ -176,8 +180,8 @@ impl ResolveContext { font_width: raw_style.font_width, font_style: raw_style.font_style, font_weight: raw_style.font_weight, - font_variations: self.resolve_variations(&raw_style.font_variations), - font_features: self.resolve_features(&raw_style.font_features), + font_variations: self.resolve_variations(raw_style.font_variations.as_ref()), + font_features: self.resolve_features(raw_style.font_features.as_ref()), locale: raw_style .locale .and_then(|v| icu_locale_core::Locale::try_from_str(v).map(|v| v.id).ok()), @@ -262,19 +266,10 @@ impl ResolveContext { /// Resolves font variation settings. pub(crate) fn resolve_variations( &mut self, - variations: &FontVariations<'_>, + variations: &[FontVariation], ) -> Resolved { - match variations { - FontVariations::Source(source) => { - self.tmp_variations.clear(); - self.tmp_variations - .extend(FontVariation::parse_css_list(source).map_while(Result::ok)); - } - FontVariations::List(settings) => { - self.tmp_variations.clear(); - self.tmp_variations.extend_from_slice(settings); - } - } + self.tmp_variations.clear(); + self.tmp_variations.extend_from_slice(variations); if self.tmp_variations.is_empty() { return Resolved::default(); } @@ -285,21 +280,9 @@ impl ResolveContext { } /// Resolves font feature settings. - pub(crate) fn resolve_features( - &mut self, - features: &FontFeatures<'_>, - ) -> Resolved { - match features { - FontFeatures::Source(source) => { - self.tmp_features.clear(); - self.tmp_features - .extend(FontFeature::parse_css_list(source).map_while(Result::ok)); - } - FontFeatures::List(settings) => { - self.tmp_features.clear(); - self.tmp_features.extend_from_slice(settings); - } - } + pub(crate) fn resolve_features(&mut self, features: &[FontFeature]) -> Resolved { + self.tmp_features.clear(); + self.tmp_features.extend_from_slice(features); if self.tmp_features.is_empty() { return Resolved::default(); } diff --git a/parley/src/setting.rs b/parley/src/setting.rs deleted file mode 100644 index 3a0f02b61..000000000 --- a/parley/src/setting.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2025 the Parley Authors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -//! OpenType settings (features and variations). - -pub use text_primitives::{FontFeature, FontVariation, Tag}; diff --git a/parley/src/style/font.rs b/parley/src/style/font.rs index 57f8007cf..416b92ca1 100644 --- a/parley/src/style/font.rs +++ b/parley/src/style/font.rs @@ -3,74 +3,148 @@ use alloc::borrow::Cow; -pub use crate::setting::{FontFeature, FontVariation}; pub use fontique::{FontStyle, FontWeight, FontWidth, GenericFamily}; -pub use text_primitives::{FontFamily, FontFamilyName}; +pub use text_primitives::{FontFamily, FontFamilyName, FontFeature, FontVariation, Tag}; -/// Font variation settings that can be supplied as a raw source string or a parsed slice. +/// Font variation settings (OpenType axis values). +/// +/// Parley requires typed settings; if you have CSS-like strings, parse them up-front with +/// [`FontVariation::parse_css_list`] and then pass the resulting slice to Parley. +/// +/// ``` +/// # use parley::{FontVariation, FontVariations, StyleProperty}; +/// # +/// let variations_vec: Vec<_> = FontVariation::parse_css_list(r#""wght" 700, "wdth" 125.5"#) +/// .collect::>() +/// .unwrap(); +/// +/// let property: StyleProperty<'_, ()> = variations_vec.as_slice().into(); +/// ``` #[derive(Clone, PartialEq, Debug)] -pub enum FontVariations<'a> { - /// Setting source in CSS format. - Source(Cow<'a, str>), - /// List of settings. - List(Cow<'a, [FontVariation]>), -} +pub struct FontVariations<'a>(Cow<'a, [FontVariation]>); impl<'a> FontVariations<'a> { /// Creates an empty list of font variations. + #[inline] pub const fn empty() -> Self { - Self::List(Cow::Borrowed(&[])) + Self(Cow::Borrowed(&[])) } -} -impl<'a> From<&'a str> for FontVariations<'a> { - fn from(value: &'a str) -> Self { - Self::Source(Cow::Borrowed(value)) + /// Returns the settings as a slice. + #[inline] + pub fn as_slice(&self) -> &[FontVariation] { + self.0.as_ref() } } impl<'a> From<&'a [FontVariation]> for FontVariations<'a> { fn from(value: &'a [FontVariation]) -> Self { - Self::List(Cow::Borrowed(value)) + Self(Cow::Borrowed(value)) } } impl<'a, const N: usize> From<&'a [FontVariation; N]> for FontVariations<'a> { fn from(value: &'a [FontVariation; N]) -> Self { - Self::List(Cow::Borrowed(&value[..])) + Self(Cow::Borrowed(&value[..])) } } -/// Font feature settings that can be supplied as a raw source string or a parsed slice. -#[derive(Clone, PartialEq, Debug)] -pub enum FontFeatures<'a> { - /// Setting source in CSS format. - Source(Cow<'a, str>), - /// List of settings. - List(Cow<'a, [FontFeature]>), +impl AsRef<[FontVariation]> for FontVariations<'_> { + #[inline] + fn as_ref(&self) -> &[FontVariation] { + self.0.as_ref() + } } +/// Font feature settings (OpenType feature values). +/// +/// Parley requires typed settings; if you have CSS-like strings, parse them up-front with +/// [`FontFeature::parse_css_list`] and then pass the resulting slice to Parley. +/// +/// ``` +/// # use parley::{FontFeature, FontFeatures, StyleProperty}; +/// # +/// let features_vec: Vec<_> = FontFeature::parse_css_list(r#""liga" on, "kern" off, "salt" 3"#) +/// .collect::>() +/// .unwrap(); +/// +/// let property: StyleProperty<'_, ()> = features_vec.as_slice().into(); +/// ``` +#[derive(Clone, PartialEq, Debug)] +pub struct FontFeatures<'a>(Cow<'a, [FontFeature]>); + impl<'a> FontFeatures<'a> { /// Creates an empty list of font features. + #[inline] pub const fn empty() -> Self { - Self::List(Cow::Borrowed(&[])) + Self(Cow::Borrowed(&[])) } -} -impl<'a> From<&'a str> for FontFeatures<'a> { - fn from(value: &'a str) -> Self { - Self::Source(Cow::Borrowed(value)) + /// Returns the settings as a slice. + #[inline] + pub fn as_slice(&self) -> &[FontFeature] { + self.0.as_ref() } } impl<'a> From<&'a [FontFeature]> for FontFeatures<'a> { fn from(value: &'a [FontFeature]) -> Self { - Self::List(Cow::Borrowed(value)) + Self(Cow::Borrowed(value)) } } impl<'a, const N: usize> From<&'a [FontFeature; N]> for FontFeatures<'a> { fn from(value: &'a [FontFeature; N]) -> Self { - Self::List(Cow::Borrowed(&value[..])) + Self(Cow::Borrowed(&value[..])) + } +} + +impl AsRef<[FontFeature]> for FontFeatures<'_> { + #[inline] + fn as_ref(&self) -> &[FontFeature] { + self.0.as_ref() + } +} + +#[cfg(test)] +mod tests { + extern crate alloc; + + use alloc::vec::Vec; + + use super::{FontFeature, FontFeatures, FontVariation, FontVariations}; + use crate::StyleProperty; + + #[test] + fn opentype_settings_from_parse_css_list() { + let features_vec: Vec<_> = + FontFeature::parse_css_list(r#""liga" on, "kern" off, "salt" 3"#) + .collect::>() + .unwrap(); + let variations_vec: Vec<_> = FontVariation::parse_css_list(r#""wght" 700, "wdth" 125.5"#) + .collect::>() + .unwrap(); + + let features = FontFeatures::from(features_vec.as_slice()); + let variations = FontVariations::from(variations_vec.as_slice()); + + let features_prop: StyleProperty<'_, ()> = features.clone().into(); + let variations_prop: StyleProperty<'_, ()> = variations.clone().into(); + + assert_eq!(features.as_ref(), features_vec.as_slice()); + assert_eq!(variations.as_ref(), variations_vec.as_slice()); + + match features_prop { + StyleProperty::FontFeatures(list) => { + assert_eq!(list.as_ref(), features_vec.as_slice()); + } + _ => panic!("expected StyleProperty::FontFeatures"), + } + match variations_prop { + StyleProperty::FontVariations(list) => { + assert_eq!(list.as_ref(), variations_vec.as_slice()); + } + _ => panic!("expected StyleProperty::FontVariations"), + } } } diff --git a/parley/src/style/mod.rs b/parley/src/style/mod.rs index 328121bc5..ecb7c3d94 100644 --- a/parley/src/style/mod.rs +++ b/parley/src/style/mod.rs @@ -12,7 +12,7 @@ use alloc::borrow::Cow; pub use brush::*; pub use font::{ FontFamily, FontFamilyName, FontFeature, FontFeatures, FontStyle, FontVariation, - FontVariations, FontWeight, FontWidth, GenericFamily, + FontVariations, FontWeight, FontWidth, GenericFamily, Tag, }; pub use styleset::StyleSet; pub use text_primitives::{OverflowWrap, TextWrapMode, WordBreak}; @@ -218,17 +218,47 @@ impl<'a, B: Brush> From> for StyleProperty<'a, B> { } impl<'a, B: Brush> From> for StyleProperty<'a, B> { + #[inline] fn from(value: FontVariations<'a>) -> Self { StyleProperty::FontVariations(value) } } +impl<'a, B: Brush> From<&'a [FontVariation]> for StyleProperty<'a, B> { + #[inline] + fn from(value: &'a [FontVariation]) -> Self { + StyleProperty::FontVariations(value.into()) + } +} + +impl<'a, B: Brush, const N: usize> From<&'a [FontVariation; N]> for StyleProperty<'a, B> { + #[inline] + fn from(value: &'a [FontVariation; N]) -> Self { + StyleProperty::FontVariations(value.into()) + } +} + impl<'a, B: Brush> From> for StyleProperty<'a, B> { + #[inline] fn from(value: FontFeatures<'a>) -> Self { StyleProperty::FontFeatures(value) } } +impl<'a, B: Brush> From<&'a [FontFeature]> for StyleProperty<'a, B> { + #[inline] + fn from(value: &'a [FontFeature]) -> Self { + StyleProperty::FontFeatures(value.into()) + } +} + +impl<'a, B: Brush, const N: usize> From<&'a [FontFeature; N]> for StyleProperty<'a, B> { + #[inline] + fn from(value: &'a [FontFeature; N]) -> Self { + StyleProperty::FontFeatures(value.into()) + } +} + impl From for StyleProperty<'_, B> { fn from(f: GenericFamily) -> Self { StyleProperty::FontFamily(f.into()) diff --git a/parley/src/tests/test_basic.rs b/parley/src/tests/test_basic.rs index 3ac0b3b9b..c5a10449a 100644 --- a/parley/src/tests/test_basic.rs +++ b/parley/src/tests/test_basic.rs @@ -9,11 +9,11 @@ use peniko::{ use super::utils::{ ColorBrush, FONT_FAMILY_LIST, TestEnv, asserts::assert_eq_layout_data_alignments, }; -use crate::setting::{FontFeature, FontVariation}; use crate::{ - Alignment, AlignmentOptions, ContentWidths, FontFamily, FontFeatures, FontVariations, - InlineBox, Layout, LineHeight, StyleProperty, TextStyle, WhiteSpaceCollapse, test_name, + Alignment, AlignmentOptions, ContentWidths, FontFamily, InlineBox, Layout, LineHeight, + StyleProperty, TextStyle, WhiteSpaceCollapse, test_name, }; +use crate::{FontFeature, FontVariation, Tag}; #[test] fn plain_multiline_text() { @@ -613,17 +613,17 @@ fn font_features() { let text = "fi ".repeat(4); let mut builder = env.ranged_builder(&text); builder.push( - FontFeatures::from(&[FontFeature { - tag: crate::setting::Tag::new(b"liga"), + &[FontFeature { + tag: Tag::new(b"liga"), value: 1, - }]), + }], 0..5, ); builder.push( - FontFeatures::from(&[FontFeature { - tag: crate::setting::Tag::new(b"liga"), + &[FontFeature { + tag: Tag::new(b"liga"), value: 0, - }]), + }], 5..10, ); let mut layout = builder.build(&text); @@ -641,10 +641,7 @@ fn variable_fonts() { for wght in [100., 500., 1000.] { let mut builder = env.ranged_builder(text); builder.push_default(FontFamily::named("Arimo")); - builder.push_default(FontVariations::from(&[FontVariation::new( - crate::setting::Tag::new(b"wght"), - wght, - )])); + builder.push_default(&[FontVariation::new(Tag::new(b"wght"), wght)]); let mut layout = builder.build(text); layout.break_all_lines(Some(100.0)); layout.align(None, Alignment::Start, AlignmentOptions::default()); diff --git a/text_primitives/src/tag.rs b/text_primitives/src/tag.rs index 220ba83c9..1a64d62ea 100644 --- a/text_primitives/src/tag.rs +++ b/text_primitives/src/tag.rs @@ -4,6 +4,9 @@ use core::fmt; /// A 4-byte OpenType tag (for example `wght`, `liga`). +/// +/// This is used by [`FontFeature`] and [`FontVariation`] to identify the OpenType feature or axis +/// being configured. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[repr(transparent)] pub struct Tag([u8; 4]);