From deee5656240cc5f2e10a3ec097a5fbaa88608016 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sat, 7 Dec 2024 23:20:34 +0700 Subject: [PATCH 01/16] Move fontique/attributes.rs to a shared styled_text. --- Cargo.lock | 9 + Cargo.toml | 2 + fontique/Cargo.toml | 5 +- fontique/src/attributes.rs | 372 +-------------------------------- fontique/src/family.rs | 7 +- fontique/src/font.rs | 2 +- fontique/src/lib.rs | 3 +- fontique/src/matching.rs | 2 +- parley/Cargo.toml | 5 +- parley/src/style/font.rs | 5 +- styled_text/Cargo.toml | 23 +++ styled_text/README.md | 0 styled_text/src/attributes.rs | 378 ++++++++++++++++++++++++++++++++++ styled_text/src/lib.rs | 21 ++ 14 files changed, 448 insertions(+), 386 deletions(-) create mode 100644 styled_text/Cargo.toml create mode 100644 styled_text/README.md create mode 100644 styled_text/src/attributes.rs create mode 100644 styled_text/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 5b4c1386..8454440a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -977,6 +977,7 @@ dependencies = [ "read-fonts", "roxmltree", "smallvec", + "styled_text", "unicode-script", "windows 0.58.0", "windows-core 0.58.0", @@ -1979,6 +1980,7 @@ dependencies = [ "hashbrown 0.15.2", "peniko", "skrifa", + "styled_text", "swash", "tiny-skia", ] @@ -2450,6 +2452,13 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "styled_text" +version = "0.1.0" +dependencies = [ + "core_maths", +] + [[package]] name = "svg_fmt" version = "0.4.3" diff --git a/Cargo.toml b/Cargo.toml index 82633df3..b800b9c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ "fontique", + "styled_text", "parley", "examples/tiny_skia_render", "examples/swash_render", @@ -84,4 +85,5 @@ parley = { version = "0.2.0", default-features = false, path = "parley" } peniko = { version = "0.2.0", default-features = false } skrifa = { version = "0.22.3", default-features = false } read-fonts = { version = "0.22.3", default-features = false } +styled_text = { version = "0.1.0", default-features = false, path = "styled_text" } swash = { version = "0.1.19", default-features = false } diff --git a/fontique/Cargo.toml b/fontique/Cargo.toml index f1b348bb..f192ad58 100644 --- a/fontique/Cargo.toml +++ b/fontique/Cargo.toml @@ -17,8 +17,8 @@ workspace = true [features] default = ["system"] -std = ["read-fonts/std", "peniko/std", "dep:memmap2"] -libm = ["read-fonts/libm", "peniko/libm", "dep:core_maths"] +std = ["read-fonts/std", "peniko/std", "styled_text/std", "dep:memmap2"] +libm = ["read-fonts/libm", "peniko/libm", "styled_text/libm", "dep:core_maths"] icu_properties = ["dep:icu_properties"] unicode_script = ["dep:unicode-script"] # Enables support for system font backends @@ -32,6 +32,7 @@ system = [ [dependencies] read-fonts = { workspace = true } peniko = { workspace = true } +styled_text = { workspace = true } smallvec = "1.13.2" memmap2 = { version = "0.9.5", optional = true } unicode-script = { version = "0.5.7", optional = true } diff --git a/fontique/src/attributes.rs b/fontique/src/attributes.rs index 17c525cd..371cce32 100644 --- a/fontique/src/attributes.rs +++ b/fontique/src/attributes.rs @@ -8,6 +8,7 @@ use core_maths::CoreFloat; use core::fmt; +use styled_text::{Stretch, Style, Weight}; /// Primary attributes for font matching: [`Stretch`], [`Style`] and [`Weight`]. /// @@ -42,374 +43,3 @@ impl fmt::Display for Attributes { ) } } - -/// Visual width of a font-- a relative change from the normal aspect -/// ratio, typically in the range `0.5` to `2.0`. -/// -/// The default value is [`Stretch::NORMAL`] or `1.0`. -/// -/// In variable fonts, this can be controlled with the `wdth` [axis]. -/// -/// See -/// -/// In CSS, this corresponds to the [`font-width`] property. -/// -/// [axis]: crate::AxisInfo -/// [`font-width`]: https://www.w3.org/TR/css-fonts-4/#font-width-prop -#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] -pub struct Stretch(f32); - -impl Stretch { - /// Width that is 50% of normal. - pub const ULTRA_CONDENSED: Self = Self(0.5); - - /// Width that is 62.5% of normal. - pub const EXTRA_CONDENSED: Self = Self(0.625); - - /// Width that is 75% of normal. - pub const CONDENSED: Self = Self(0.75); - - /// Width that is 87.5% of normal. - pub const SEMI_CONDENSED: Self = Self(0.875); - - /// Width that is 100% of normal. This is the default value. - pub const NORMAL: Self = Self(1.0); - - /// Width that is 112.5% of normal. - pub const SEMI_EXPANDED: Self = Self(1.125); - - /// Width that is 125% of normal. - pub const EXPANDED: Self = Self(1.25); - - /// Width that is 150% of normal. - pub const EXTRA_EXPANDED: Self = Self(1.5); - - /// Width that is 200% of normal. - pub const ULTRA_EXPANDED: Self = Self(2.0); -} - -impl Stretch { - /// Creates a new stretch attribute with the given ratio. - /// - /// This can also be created [from a percentage](Self::from_percentage). - /// - /// # Example - /// - /// ``` - /// # use fontique::Stretch; - /// assert_eq!(Stretch::from_ratio(1.5), Stretch::EXTRA_EXPANDED); - /// ``` - pub fn from_ratio(ratio: f32) -> Self { - Self(ratio) - } - - /// Creates a stretch attribute from a percentage. - /// - /// This can also be created [from a ratio](Self::from_ratio). - /// - /// # Example - /// - /// ``` - /// # use fontique::Stretch; - /// assert_eq!(Stretch::from_percentage(87.5), Stretch::SEMI_CONDENSED); - /// ``` - pub fn from_percentage(percentage: f32) -> Self { - Self(percentage / 100.0) - } - - /// Returns the stretch attribute as a ratio. - /// - /// This is a linear scaling factor with `1.0` being "normal" width. - /// - /// # Example - /// - /// ``` - /// # use fontique::Stretch; - /// assert_eq!(Stretch::NORMAL.ratio(), 1.0); - /// ``` - pub fn ratio(self) -> f32 { - self.0 - } - - /// Returns the stretch attribute as a percentage value. - /// - /// This is generally the value associated with the `wdth` axis. - pub fn percentage(self) -> f32 { - self.0 * 100.0 - } - - /// Returns `true` if the stretch is [normal]. - /// - /// [normal]: Stretch::NORMAL - pub fn is_normal(self) -> bool { - self == Self::NORMAL - } - - /// Returns `true` if the stretch is condensed (less than [normal]). - /// - /// [normal]: Stretch::NORMAL - pub fn is_condensed(self) -> bool { - self < Self::NORMAL - } - - /// Returns `true` if the stretch is expanded (greater than [normal]). - /// - /// [normal]: Stretch::NORMAL - pub fn is_expanded(self) -> bool { - self > Self::NORMAL - } - - /// Parses the stretch from a CSS style keyword or a percentage value. - /// - /// # Examples - /// - /// ``` - /// # use fontique::Stretch; - /// assert_eq!(Stretch::parse("semi-condensed"), Some(Stretch::SEMI_CONDENSED)); - /// assert_eq!(Stretch::parse("80%"), Some(Stretch::from_percentage(80.0))); - /// assert_eq!(Stretch::parse("wideload"), None); - /// ``` - pub fn parse(s: &str) -> Option { - let s = s.trim(); - Some(match s { - "ultra-condensed" => Self::ULTRA_CONDENSED, - "extra-condensed" => Self::EXTRA_CONDENSED, - "condensed" => Self::CONDENSED, - "semi-condensed" => Self::SEMI_CONDENSED, - "normal" => Self::NORMAL, - "semi-expanded" => Self::SEMI_EXPANDED, - "extra-expanded" => Self::EXTRA_EXPANDED, - "ultra-expanded" => Self::ULTRA_EXPANDED, - _ => { - if s.ends_with('%') { - let p = s.get(..s.len() - 1)?.parse::().ok()?; - return Some(Self::from_percentage(p)); - } - return None; - } - }) - } -} - -impl fmt::Display for Stretch { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let value = self.0 * 1000.0; - if value.fract() == 0.0 { - let keyword = match value as i32 { - 500 => "ultra-condensed", - 625 => "extra-condensed", - 750 => "condensed", - 875 => "semi-condensed", - 1000 => "normal", - 1125 => "semi-expanded", - 1250 => "expanded", - 1500 => "extra-expanded", - 2000 => "ultra-expanded", - _ => { - return write!(f, "{}%", self.percentage()); - } - }; - write!(f, "{}", keyword) - } else { - write!(f, "{}%", self.percentage()) - } - } -} - -impl Default for Stretch { - fn default() -> Self { - Self::NORMAL - } -} - -/// Visual weight class of a font, typically on a scale from 1.0 to 1000.0. -/// -/// The default value is [`Weight::NORMAL`] or `400.0`. -/// -/// In variable fonts, this can be controlled with the `wght` [axis]. -/// -/// See -/// -/// In CSS, this corresponds to the [`font-weight`] property. -/// -/// [axis]: crate::AxisInfo -/// [`font-weight`]: https://www.w3.org/TR/css-fonts-4/#font-weight-prop -#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] -pub struct Weight(f32); - -impl Weight { - /// Weight value of 100. - pub const THIN: Self = Self(100.0); - - /// Weight value of 200. - pub const EXTRA_LIGHT: Self = Self(200.0); - - /// Weight value of 300. - pub const LIGHT: Self = Self(300.0); - - /// Weight value of 350. - pub const SEMI_LIGHT: Self = Self(350.0); - - /// Weight value of 400. This is the default value. - pub const NORMAL: Self = Self(400.0); - - /// Weight value of 500. - pub const MEDIUM: Self = Self(500.0); - - /// Weight value of 600. - pub const SEMI_BOLD: Self = Self(600.0); - - /// Weight value of 700. - pub const BOLD: Self = Self(700.0); - - /// Weight value of 800. - pub const EXTRA_BOLD: Self = Self(800.0); - - /// Weight value of 900. - pub const BLACK: Self = Self(900.0); - - /// Weight value of 950. - pub const EXTRA_BLACK: Self = Self(950.0); -} - -impl Weight { - /// Creates a new weight attribute with the given value. - pub fn new(weight: f32) -> Self { - Self(weight) - } - - /// Returns the underlying weight value. - pub fn value(self) -> f32 { - self.0 - } - - /// Parses a CSS style font weight attribute. - /// - /// # Examples - /// - /// ``` - /// # use fontique::Weight; - /// assert_eq!(Weight::parse("normal"), Some(Weight::NORMAL)); - /// assert_eq!(Weight::parse("bold"), Some(Weight::BOLD)); - /// assert_eq!(Weight::parse("850"), Some(Weight::new(850.0))); - /// assert_eq!(Weight::parse("invalid"), None); - /// ``` - pub fn parse(s: &str) -> Option { - let s = s.trim(); - Some(match s { - "normal" => Self::NORMAL, - "bold" => Self::BOLD, - _ => Self(s.parse::().ok()?), - }) - } -} - -impl Default for Weight { - fn default() -> Self { - Self::NORMAL - } -} - -impl fmt::Display for Weight { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let value = self.0; - if value.fract() == 0.0 { - let keyword = match value as i32 { - 100 => "thin", - 200 => "extra-light", - 300 => "light", - 400 => "normal", - 500 => "medium", - 600 => "semi-bold", - 700 => "bold", - 800 => "extra-bold", - 900 => "black", - _ => return write!(f, "{}", self.0), - }; - write!(f, "{}", keyword) - } else { - write!(f, "{}", self.0) - } - } -} - -/// Visual style or 'slope' of a font. -/// -/// The default value is [`Style::Normal`]. -/// -/// In variable fonts, this can be controlled with the `ital` -/// and `slnt` [axes] for italic and oblique styles, respectively. -/// -/// See -/// -/// In CSS, this corresponds to the [`font-style`] property. -/// -/// [axes]: crate::AxisInfo -/// [`font-style`]: https://www.w3.org/TR/css-fonts-4/#font-style-prop -#[derive(Copy, Clone, PartialEq, Default, Debug)] -pub enum Style { - /// An upright or "roman" style. - #[default] - Normal, - /// Generally a slanted style, originally based on semi-cursive forms. - /// This often has a different structure from the normal style. - Italic, - /// Oblique (or slanted) style with an optional angle in degrees, - /// counter-clockwise from the vertical. - Oblique(Option), -} - -impl Style { - /// Parses a font style from a CSS value. - pub fn parse(mut s: &str) -> Option { - s = s.trim(); - Some(match s { - "normal" => Self::Normal, - "italic" => Self::Italic, - "oblique" => Self::Oblique(Some(14.)), - _ => { - if s.starts_with("oblique ") { - s = s.get(8..)?; - if s.ends_with("deg") { - s = s.get(..s.len() - 3)?; - if let Ok(degrees) = s.trim().parse::() { - return Some(Self::Oblique(Some(degrees))); - } - } else if s.ends_with("grad") { - s = s.get(..s.len() - 4)?; - if let Ok(gradians) = s.trim().parse::() { - return Some(Self::Oblique(Some(gradians / 400.0 * 360.0))); - } - } else if s.ends_with("rad") { - s = s.get(..s.len() - 3)?; - if let Ok(radians) = s.trim().parse::() { - return Some(Self::Oblique(Some(radians.to_degrees()))); - } - } else if s.ends_with("turn") { - s = s.get(..s.len() - 4)?; - if let Ok(turns) = s.trim().parse::() { - return Some(Self::Oblique(Some(turns * 360.0))); - } - } - return Some(Self::Oblique(None)); - } - return None; - } - }) - } -} - -impl fmt::Display for Style { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let value = match self { - Self::Normal => "normal", - Self::Italic => "italic", - Self::Oblique(None) => "oblique", - Self::Oblique(Some(degrees)) if *degrees == 14.0 => "oblique", - Self::Oblique(Some(degrees)) => { - return write!(f, "oblique({}deg)", degrees); - } - }; - write!(f, "{}", value) - } -} diff --git a/fontique/src/family.rs b/fontique/src/family.rs index c017cba7..b6a7e994 100644 --- a/fontique/src/family.rs +++ b/fontique/src/family.rs @@ -3,14 +3,11 @@ //! Model for font families. -use super::{ - attributes::{Stretch, Style, Weight}, - family_name::FamilyName, - font::FontInfo, -}; +use super::{family_name::FamilyName, font::FontInfo}; use alloc::sync::Arc; use core::sync::atomic::{AtomicU64, Ordering}; use smallvec::SmallVec; +use styled_text::{Stretch, Style, Weight}; /// Unique identifier for a font family. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] diff --git a/fontique/src/font.rs b/fontique/src/font.rs index 19982c32..0f996462 100644 --- a/fontique/src/font.rs +++ b/fontique/src/font.rs @@ -3,11 +3,11 @@ //! Model for a font. -use super::attributes::{Stretch, Style, Weight}; use super::source::{SourceInfo, SourceKind}; use super::{source_cache::SourceCache, Blob}; use read_fonts::{types::Tag, FontRef, TableProvider as _}; use smallvec::SmallVec; +use styled_text::{Stretch, Style, Weight}; type AxisVec = SmallVec<[AxisInfo; 1]>; diff --git a/fontique/src/lib.rs b/fontique/src/lib.rs index 89f70f9e..3753ee0f 100644 --- a/fontique/src/lib.rs +++ b/fontique/src/lib.rs @@ -48,8 +48,9 @@ mod source_cache; pub use icu_locid::LanguageIdentifier as Language; pub use peniko::Blob; +pub use styled_text::{Stretch, Style, Weight}; -pub use attributes::{Attributes, Stretch, Style, Weight}; +pub use attributes::Attributes; pub use collection::{Collection, CollectionOptions, Query, QueryFamily, QueryFont, QueryStatus}; pub use fallback::FallbackKey; pub use family::{FamilyId, FamilyInfo}; diff --git a/fontique/src/matching.rs b/fontique/src/matching.rs index af708f3d..f3b28e89 100644 --- a/fontique/src/matching.rs +++ b/fontique/src/matching.rs @@ -3,9 +3,9 @@ //! Implementation of the CSS font matching algorithm. -use super::attributes::{Stretch, Style, Weight}; use super::font::FontInfo; use smallvec::SmallVec; +use styled_text::{Stretch, Style, Weight}; const DEFAULT_OBLIQUE_ANGLE: f32 = 14.0; diff --git a/parley/Cargo.toml b/parley/Cargo.toml index 7d89a993..4f5f0c24 100644 --- a/parley/Cargo.toml +++ b/parley/Cargo.toml @@ -18,8 +18,8 @@ workspace = true [features] default = ["system"] -std = ["fontique/std", "skrifa/std", "peniko/std"] -libm = ["fontique/libm", "skrifa/libm", "peniko/libm", "dep:core_maths"] +std = ["fontique/std", "skrifa/std", "peniko/std", "styled_text/std"] +libm = ["fontique/libm", "skrifa/libm", "peniko/libm", "styled_text/libm", "dep:core_maths"] # Enables support for system font backends system = ["std", "fontique/system"] accesskit = ["dep:accesskit"] @@ -32,6 +32,7 @@ fontique = { workspace = true } core_maths = { version = "0.1.0", optional = true } accesskit = { workspace = true, optional = true } hashbrown = { workspace = true } +styled_text = { workspace = true } [dev-dependencies] tiny-skia = "0.11.4" diff --git a/parley/src/style/font.rs b/parley/src/style/font.rs index 473dc967..1b38f796 100644 --- a/parley/src/style/font.rs +++ b/parley/src/style/font.rs @@ -5,9 +5,8 @@ use alloc::borrow::Cow; use alloc::borrow::ToOwned; use core::fmt; -pub use fontique::{ - GenericFamily, Stretch as FontStretch, Style as FontStyle, Weight as FontWeight, -}; +pub use fontique::GenericFamily; +pub use styled_text::{Stretch as FontStretch, Style as FontStyle, Weight as FontWeight}; /// Setting for a font variation. pub type FontVariation = swash::Setting; diff --git a/styled_text/Cargo.toml b/styled_text/Cargo.toml new file mode 100644 index 00000000..d9df4915 --- /dev/null +++ b/styled_text/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "styled_text" +version = "0.1.0" # Keep in sync with workspace dependency specification +description = "Styled text and text styles" +keywords = ["font", "text"] +categories = ["gui", "os"] +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true + +[package.metadata.docs.rs] +all-features = true + +[lints] +workspace = true + +[features] +std = [] +libm = ["dep:core_maths"] + +[dependencies] +core_maths = { version = "0.1.0", optional = true } diff --git a/styled_text/README.md b/styled_text/README.md new file mode 100644 index 00000000..e69de29b diff --git a/styled_text/src/attributes.rs b/styled_text/src/attributes.rs new file mode 100644 index 00000000..af96013b --- /dev/null +++ b/styled_text/src/attributes.rs @@ -0,0 +1,378 @@ +// Copyright 2024 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Properties for specifying font weight, stretch and style. + +#[cfg(feature = "libm")] +#[allow(unused_imports)] +use core_maths::CoreFloat; + +use core::fmt; + +/// Visual width of a font-- a relative change from the normal aspect +/// ratio, typically in the range `0.5` to `2.0`. +/// +/// The default value is [`Stretch::NORMAL`] or `1.0`. +/// +/// In variable fonts, this can be controlled with the `wdth` axis. +/// +/// See +/// +/// In CSS, this corresponds to the [`font-width`] property. +/// +/// [`font-width`]: https://www.w3.org/TR/css-fonts-4/#font-width-prop +#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] +pub struct Stretch(f32); + +impl Stretch { + /// Width that is 50% of normal. + pub const ULTRA_CONDENSED: Self = Self(0.5); + + /// Width that is 62.5% of normal. + pub const EXTRA_CONDENSED: Self = Self(0.625); + + /// Width that is 75% of normal. + pub const CONDENSED: Self = Self(0.75); + + /// Width that is 87.5% of normal. + pub const SEMI_CONDENSED: Self = Self(0.875); + + /// Width that is 100% of normal. This is the default value. + pub const NORMAL: Self = Self(1.0); + + /// Width that is 112.5% of normal. + pub const SEMI_EXPANDED: Self = Self(1.125); + + /// Width that is 125% of normal. + pub const EXPANDED: Self = Self(1.25); + + /// Width that is 150% of normal. + pub const EXTRA_EXPANDED: Self = Self(1.5); + + /// Width that is 200% of normal. + pub const ULTRA_EXPANDED: Self = Self(2.0); +} + +impl Stretch { + /// Creates a new stretch attribute with the given ratio. + /// + /// This can also be created [from a percentage](Self::from_percentage). + /// + /// # Example + /// + /// ``` + /// # use styled_text::Stretch; + /// assert_eq!(Stretch::from_ratio(1.5), Stretch::EXTRA_EXPANDED); + /// ``` + pub fn from_ratio(ratio: f32) -> Self { + Self(ratio) + } + + /// Creates a stretch attribute from a percentage. + /// + /// This can also be created [from a ratio](Self::from_ratio). + /// + /// # Example + /// + /// ``` + /// # use styled_text::Stretch; + /// assert_eq!(Stretch::from_percentage(87.5), Stretch::SEMI_CONDENSED); + /// ``` + pub fn from_percentage(percentage: f32) -> Self { + Self(percentage / 100.0) + } + + /// Returns the stretch attribute as a ratio. + /// + /// This is a linear scaling factor with `1.0` being "normal" width. + /// + /// # Example + /// + /// ``` + /// # use styled_text::Stretch; + /// assert_eq!(Stretch::NORMAL.ratio(), 1.0); + /// ``` + pub fn ratio(self) -> f32 { + self.0 + } + + /// Returns the stretch attribute as a percentage value. + /// + /// This is generally the value associated with the `wdth` axis. + pub fn percentage(self) -> f32 { + self.0 * 100.0 + } + + /// Returns `true` if the stretch is [normal]. + /// + /// [normal]: Stretch::NORMAL + pub fn is_normal(self) -> bool { + self == Self::NORMAL + } + + /// Returns `true` if the stretch is condensed (less than [normal]). + /// + /// [normal]: Stretch::NORMAL + pub fn is_condensed(self) -> bool { + self < Self::NORMAL + } + + /// Returns `true` if the stretch is expanded (greater than [normal]). + /// + /// [normal]: Stretch::NORMAL + pub fn is_expanded(self) -> bool { + self > Self::NORMAL + } + + /// Parses the stretch from a CSS style keyword or a percentage value. + /// + /// # Examples + /// + /// ``` + /// # use styled_text::Stretch; + /// assert_eq!(Stretch::parse("semi-condensed"), Some(Stretch::SEMI_CONDENSED)); + /// assert_eq!(Stretch::parse("80%"), Some(Stretch::from_percentage(80.0))); + /// assert_eq!(Stretch::parse("wideload"), None); + /// ``` + pub fn parse(s: &str) -> Option { + let s = s.trim(); + Some(match s { + "ultra-condensed" => Self::ULTRA_CONDENSED, + "extra-condensed" => Self::EXTRA_CONDENSED, + "condensed" => Self::CONDENSED, + "semi-condensed" => Self::SEMI_CONDENSED, + "normal" => Self::NORMAL, + "semi-expanded" => Self::SEMI_EXPANDED, + "extra-expanded" => Self::EXTRA_EXPANDED, + "ultra-expanded" => Self::ULTRA_EXPANDED, + _ => { + if s.ends_with('%') { + let p = s.get(..s.len() - 1)?.parse::().ok()?; + return Some(Self::from_percentage(p)); + } + return None; + } + }) + } +} + +impl fmt::Display for Stretch { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let value = self.0 * 1000.0; + if value.fract() == 0.0 { + let keyword = match value as i32 { + 500 => "ultra-condensed", + 625 => "extra-condensed", + 750 => "condensed", + 875 => "semi-condensed", + 1000 => "normal", + 1125 => "semi-expanded", + 1250 => "expanded", + 1500 => "extra-expanded", + 2000 => "ultra-expanded", + _ => { + return write!(f, "{}%", self.percentage()); + } + }; + write!(f, "{}", keyword) + } else { + write!(f, "{}%", self.percentage()) + } + } +} + +impl Default for Stretch { + fn default() -> Self { + Self::NORMAL + } +} + +/// Visual weight class of a font, typically on a scale from 1.0 to 1000.0. +/// +/// The default value is [`Weight::NORMAL`] or `400.0`. +/// +/// In variable fonts, this can be controlled with the `wght` axis. +/// +/// See +/// +/// In CSS, this corresponds to the [`font-weight`] property. +/// +/// [`font-weight`]: https://www.w3.org/TR/css-fonts-4/#font-weight-prop +#[derive(Copy, Clone, PartialEq, PartialOrd, Debug)] +pub struct Weight(f32); + +impl Weight { + /// Weight value of 100. + pub const THIN: Self = Self(100.0); + + /// Weight value of 200. + pub const EXTRA_LIGHT: Self = Self(200.0); + + /// Weight value of 300. + pub const LIGHT: Self = Self(300.0); + + /// Weight value of 350. + pub const SEMI_LIGHT: Self = Self(350.0); + + /// Weight value of 400. This is the default value. + pub const NORMAL: Self = Self(400.0); + + /// Weight value of 500. + pub const MEDIUM: Self = Self(500.0); + + /// Weight value of 600. + pub const SEMI_BOLD: Self = Self(600.0); + + /// Weight value of 700. + pub const BOLD: Self = Self(700.0); + + /// Weight value of 800. + pub const EXTRA_BOLD: Self = Self(800.0); + + /// Weight value of 900. + pub const BLACK: Self = Self(900.0); + + /// Weight value of 950. + pub const EXTRA_BLACK: Self = Self(950.0); +} + +impl Weight { + /// Creates a new weight attribute with the given value. + pub fn new(weight: f32) -> Self { + Self(weight) + } + + /// Returns the underlying weight value. + pub fn value(self) -> f32 { + self.0 + } + + /// Parses a CSS style font weight attribute. + /// + /// # Examples + /// + /// ``` + /// # use styled_text::Weight; + /// assert_eq!(Weight::parse("normal"), Some(Weight::NORMAL)); + /// assert_eq!(Weight::parse("bold"), Some(Weight::BOLD)); + /// assert_eq!(Weight::parse("850"), Some(Weight::new(850.0))); + /// assert_eq!(Weight::parse("invalid"), None); + /// ``` + pub fn parse(s: &str) -> Option { + let s = s.trim(); + Some(match s { + "normal" => Self::NORMAL, + "bold" => Self::BOLD, + _ => Self(s.parse::().ok()?), + }) + } +} + +impl Default for Weight { + fn default() -> Self { + Self::NORMAL + } +} + +impl fmt::Display for Weight { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let value = self.0; + if value.fract() == 0.0 { + let keyword = match value as i32 { + 100 => "thin", + 200 => "extra-light", + 300 => "light", + 400 => "normal", + 500 => "medium", + 600 => "semi-bold", + 700 => "bold", + 800 => "extra-bold", + 900 => "black", + _ => return write!(f, "{}", self.0), + }; + write!(f, "{}", keyword) + } else { + write!(f, "{}", self.0) + } + } +} + +/// Visual style or 'slope' of a font. +/// +/// The default value is [`Style::Normal`]. +/// +/// In variable fonts, this can be controlled with the `ital` +/// and `slnt` axes for italic and oblique styles, respectively. +/// +/// See +/// +/// In CSS, this corresponds to the [`font-style`] property. +/// +/// [`font-style`]: https://www.w3.org/TR/css-fonts-4/#font-style-prop +#[derive(Copy, Clone, PartialEq, Default, Debug)] +pub enum Style { + /// An upright or "roman" style. + #[default] + Normal, + /// Generally a slanted style, originally based on semi-cursive forms. + /// This often has a different structure from the normal style. + Italic, + /// Oblique (or slanted) style with an optional angle in degrees, + /// counter-clockwise from the vertical. + Oblique(Option), +} + +impl Style { + /// Parses a font style from a CSS value. + pub fn parse(mut s: &str) -> Option { + s = s.trim(); + Some(match s { + "normal" => Self::Normal, + "italic" => Self::Italic, + "oblique" => Self::Oblique(Some(14.)), + _ => { + if s.starts_with("oblique ") { + s = s.get(8..)?; + if s.ends_with("deg") { + s = s.get(..s.len() - 3)?; + if let Ok(degrees) = s.trim().parse::() { + return Some(Self::Oblique(Some(degrees))); + } + } else if s.ends_with("grad") { + s = s.get(..s.len() - 4)?; + if let Ok(gradians) = s.trim().parse::() { + return Some(Self::Oblique(Some(gradians / 400.0 * 360.0))); + } + } else if s.ends_with("rad") { + s = s.get(..s.len() - 3)?; + if let Ok(radians) = s.trim().parse::() { + return Some(Self::Oblique(Some(radians.to_degrees()))); + } + } else if s.ends_with("turn") { + s = s.get(..s.len() - 4)?; + if let Ok(turns) = s.trim().parse::() { + return Some(Self::Oblique(Some(turns * 360.0))); + } + } + return Some(Self::Oblique(None)); + } + return None; + } + }) + } +} + +impl fmt::Display for Style { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let value = match self { + Self::Normal => "normal", + Self::Italic => "italic", + Self::Oblique(None) => "oblique", + Self::Oblique(Some(degrees)) if *degrees == 14.0 => "oblique", + Self::Oblique(Some(degrees)) => { + return write!(f, "oblique({}deg)", degrees); + } + }; + write!(f, "{}", value) + } +} diff --git a/styled_text/src/lib.rs b/styled_text/src/lib.rs new file mode 100644 index 00000000..25244588 --- /dev/null +++ b/styled_text/src/lib.rs @@ -0,0 +1,21 @@ +// Copyright 2024 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Styled Text and Text Styles + +// LINEBENDER LINT SET - lib.rs - v1 +// See https://linebender.org/wiki/canonical-lints/ +// These lints aren't included in Cargo.toml because they +// shouldn't apply to examples and tests +#![warn(unused_crate_dependencies)] +#![warn(clippy::print_stdout, clippy::print_stderr)] +// END LINEBENDER LINT SET +#![allow(elided_lifetimes_in_paths)] +#![allow(missing_docs)] +#![allow(clippy::cast_possible_truncation)] +#![allow(clippy::exhaustive_enums)] +#![allow(clippy::use_self)] + +mod attributes; + +pub use attributes::{Stretch, Style, Weight}; From 91d361b99618f514907378d26bf6fcbca9ce501d Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sat, 7 Dec 2024 23:30:25 +0700 Subject: [PATCH 02/16] Move `fontique::GenericFamily` to `styled_text` --- fontique/src/generic.rs | 118 ++---------------------------------- parley/src/style/font.rs | 3 +- styled_text/src/generic.rs | 119 +++++++++++++++++++++++++++++++++++++ styled_text/src/lib.rs | 2 + 4 files changed, 126 insertions(+), 116 deletions(-) create mode 100644 styled_text/src/generic.rs diff --git a/fontique/src/generic.rs b/fontique/src/generic.rs index afbdc4ca..e48f1077 100644 --- a/fontique/src/generic.rs +++ b/fontique/src/generic.rs @@ -4,124 +4,14 @@ //! Generic font families. use super::FamilyId; -use core::fmt; use smallvec::SmallVec; -type FamilyVec = SmallVec<[FamilyId; 2]>; - -/// Describes a generic font family. -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -#[repr(u8)] -pub enum GenericFamily { - /// Glyphs have finishing strokes, flared or tapering ends, or have actual - /// serifed endings. - Serif = 0, - /// Glyphs have stroke endings that are plain. - SansSerif = 1, - /// All glyphs have the same fixed width. - Monospace = 2, - /// Glyphs in cursive fonts generally have either joining strokes or other - /// cursive characteristics beyond those of italic typefaces. The glyphs - /// are partially or completely connected, and the result looks more like - /// handwritten pen or brush writing than printed letter work. - Cursive = 3, - /// Fantasy fonts are primarily decorative fonts that contain playful - /// representations of characters - Fantasy = 4, - /// Glyphs are taken from the default user interface font on a given - /// platform. - SystemUi = 5, - /// The default user interface serif font. - UiSerif = 6, - /// The default user interface sans-serif font. - UiSansSerif = 7, - /// The default user interface monospace font. - UiMonospace = 8, - /// The default user interface font that has rounded features. - UiRounded = 9, - /// Fonts that are specifically designed to render emoji. - Emoji = 10, - /// This is for the particular stylistic concerns of representing - /// mathematics: superscript and subscript, brackets that cross several - /// lines, nesting expressions, and double struck glyphs with distinct - /// meanings. - Math = 11, - /// A particular style of Chinese characters that are between serif-style - /// Song and cursive-style Kai forms. This style is often used for - /// government documents. - FangSong = 12, -} +// FIXME(style): Other users of this can import it from styled_text +pub use styled_text::GenericFamily; -impl GenericFamily { - /// Parses a generic family from a CSS generic family name. - /// - /// # Example - /// ``` - /// # use fontique::GenericFamily; - /// assert_eq!(GenericFamily::parse("sans-serif"), Some(GenericFamily::SansSerif)); - /// assert_eq!(GenericFamily::parse("Arial"), None); - /// ``` - pub fn parse(s: &str) -> Option { - let s = s.trim(); - Some(match s { - "serif" => Self::Serif, - "sans-serif" => Self::SansSerif, - "monospace" => Self::Monospace, - "cursive" => Self::Cursive, - "fantasy" => Self::Fantasy, - "system-ui" => Self::SystemUi, - "ui-serif" => Self::UiSerif, - "ui-sans-serif" => Self::UiSansSerif, - "ui-monospace" => Self::UiMonospace, - "ui-rounded" => Self::UiRounded, - "emoji" => Self::Emoji, - "math" => Self::Math, - "fangsong" => Self::FangSong, - _ => return None, - }) - } - - /// Returns a slice containing all generic family variants. - pub const fn all() -> &'static [GenericFamily] { - &[ - GenericFamily::SansSerif, - GenericFamily::Serif, - GenericFamily::Monospace, - GenericFamily::Cursive, - GenericFamily::Fantasy, - GenericFamily::SystemUi, - GenericFamily::UiSerif, - GenericFamily::UiSansSerif, - GenericFamily::UiMonospace, - GenericFamily::UiRounded, - GenericFamily::Emoji, - GenericFamily::Math, - GenericFamily::FangSong, - ] - } -} - -impl fmt::Display for GenericFamily { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let name = match self { - Self::Serif => "serif", - Self::SansSerif => "sans-serif", - Self::Monospace => "monospace", - Self::Cursive => "cursive", - Self::Fantasy => "fantasy", - Self::SystemUi => "system-ui", - Self::UiSerif => "ui-serif", - Self::UiSansSerif => "ui-sans-serif", - Self::UiMonospace => "ui-monospace", - Self::UiRounded => "ui-rounded", - Self::Emoji => "emoji", - Self::Math => "math", - Self::FangSong => "fangsong", - }; - write!(f, "{}", name) - } -} +type FamilyVec = SmallVec<[FamilyId; 2]>; +// FIXME(style): This should be done better. const COUNT: usize = GenericFamily::FangSong as usize + 1; /// Maps generic families to family identifiers. diff --git a/parley/src/style/font.rs b/parley/src/style/font.rs index 1b38f796..759f785f 100644 --- a/parley/src/style/font.rs +++ b/parley/src/style/font.rs @@ -5,8 +5,7 @@ use alloc::borrow::Cow; use alloc::borrow::ToOwned; use core::fmt; -pub use fontique::GenericFamily; -pub use styled_text::{Stretch as FontStretch, Style as FontStyle, Weight as FontWeight}; +pub use styled_text::{GenericFamily, Stretch as FontStretch, Style as FontStyle, Weight as FontWeight}; /// Setting for a font variation. pub type FontVariation = swash::Setting; diff --git a/styled_text/src/generic.rs b/styled_text/src/generic.rs new file mode 100644 index 00000000..eb5e41d0 --- /dev/null +++ b/styled_text/src/generic.rs @@ -0,0 +1,119 @@ +// Copyright 2024 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Generic font families. + +use core::fmt; + +/// Describes a generic font family. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(u8)] +pub enum GenericFamily { + /// Glyphs have finishing strokes, flared or tapering ends, or have actual + /// serifed endings. + Serif = 0, + /// Glyphs have stroke endings that are plain. + SansSerif = 1, + /// All glyphs have the same fixed width. + Monospace = 2, + /// Glyphs in cursive fonts generally have either joining strokes or other + /// cursive characteristics beyond those of italic typefaces. The glyphs + /// are partially or completely connected, and the result looks more like + /// handwritten pen or brush writing than printed letter work. + Cursive = 3, + /// Fantasy fonts are primarily decorative fonts that contain playful + /// representations of characters + Fantasy = 4, + /// Glyphs are taken from the default user interface font on a given + /// platform. + SystemUi = 5, + /// The default user interface serif font. + UiSerif = 6, + /// The default user interface sans-serif font. + UiSansSerif = 7, + /// The default user interface monospace font. + UiMonospace = 8, + /// The default user interface font that has rounded features. + UiRounded = 9, + /// Fonts that are specifically designed to render emoji. + Emoji = 10, + /// This is for the particular stylistic concerns of representing + /// mathematics: superscript and subscript, brackets that cross several + /// lines, nesting expressions, and double struck glyphs with distinct + /// meanings. + Math = 11, + /// A particular style of Chinese characters that are between serif-style + /// Song and cursive-style Kai forms. This style is often used for + /// government documents. + FangSong = 12, +} + +impl GenericFamily { + /// Parses a generic family from a CSS generic family name. + /// + /// # Example + /// ``` + /// # use styled_text::GenericFamily; + /// assert_eq!(GenericFamily::parse("sans-serif"), Some(GenericFamily::SansSerif)); + /// assert_eq!(GenericFamily::parse("Arial"), None); + /// ``` + pub fn parse(s: &str) -> Option { + let s = s.trim(); + Some(match s { + "serif" => Self::Serif, + "sans-serif" => Self::SansSerif, + "monospace" => Self::Monospace, + "cursive" => Self::Cursive, + "fantasy" => Self::Fantasy, + "system-ui" => Self::SystemUi, + "ui-serif" => Self::UiSerif, + "ui-sans-serif" => Self::UiSansSerif, + "ui-monospace" => Self::UiMonospace, + "ui-rounded" => Self::UiRounded, + "emoji" => Self::Emoji, + "math" => Self::Math, + "fangsong" => Self::FangSong, + _ => return None, + }) + } + + /// Returns a slice containing all generic family variants. + pub const fn all() -> &'static [GenericFamily] { + &[ + GenericFamily::SansSerif, + GenericFamily::Serif, + GenericFamily::Monospace, + GenericFamily::Cursive, + GenericFamily::Fantasy, + GenericFamily::SystemUi, + GenericFamily::UiSerif, + GenericFamily::UiSansSerif, + GenericFamily::UiMonospace, + GenericFamily::UiRounded, + GenericFamily::Emoji, + GenericFamily::Math, + GenericFamily::FangSong, + ] + } +} + +impl fmt::Display for GenericFamily { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let name = match self { + Self::Serif => "serif", + Self::SansSerif => "sans-serif", + Self::Monospace => "monospace", + Self::Cursive => "cursive", + Self::Fantasy => "fantasy", + Self::SystemUi => "system-ui", + Self::UiSerif => "ui-serif", + Self::UiSansSerif => "ui-sans-serif", + Self::UiMonospace => "ui-monospace", + Self::UiRounded => "ui-rounded", + Self::Emoji => "emoji", + Self::Math => "math", + Self::FangSong => "fangsong", + }; + write!(f, "{}", name) + } +} diff --git a/styled_text/src/lib.rs b/styled_text/src/lib.rs index 25244588..f1633791 100644 --- a/styled_text/src/lib.rs +++ b/styled_text/src/lib.rs @@ -17,5 +17,7 @@ #![allow(clippy::use_self)] mod attributes; +mod generic; pub use attributes::{Stretch, Style, Weight}; +pub use generic::GenericFamily; From 1d4e60124d7b5ce88b00a65f460626b69de46b4e Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sat, 7 Dec 2024 23:43:05 +0700 Subject: [PATCH 03/16] Move `parley::style::FontFamily` to `styled_text` --- parley/src/style/font.rs | 146 +------------------------------ styled_text/src/font_family.rs | 151 +++++++++++++++++++++++++++++++++ styled_text/src/lib.rs | 7 ++ 3 files changed, 159 insertions(+), 145 deletions(-) create mode 100644 styled_text/src/font_family.rs diff --git a/parley/src/style/font.rs b/parley/src/style/font.rs index 759f785f..a890e448 100644 --- a/parley/src/style/font.rs +++ b/parley/src/style/font.rs @@ -5,7 +5,7 @@ use alloc::borrow::Cow; use alloc::borrow::ToOwned; use core::fmt; -pub use styled_text::{GenericFamily, Stretch as FontStretch, Style as FontStyle, Weight as FontWeight}; +pub use styled_text::{FontFamily, GenericFamily, Stretch as FontStretch, Style as FontStyle, Weight as FontWeight}; /// Setting for a font variation. pub type FontVariation = swash::Setting; @@ -26,69 +26,6 @@ pub enum FontStack<'a> { List(Cow<'a, [FontFamily<'a>]>), } -/// Named or generic font family. -/// -/// -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum FontFamily<'a> { - /// Named font family. - Named(Cow<'a, str>), - /// Generic font family. - Generic(GenericFamily), -} - -impl<'a> FontFamily<'a> { - /// Parses a font family containing a name or a generic family. - /// - /// # Example - /// ``` - /// # extern crate alloc; - /// use alloc::borrow::Cow; - /// use parley::style::FontFamily::{self, *}; - /// use parley::style::GenericFamily::*; - /// - /// assert_eq!(FontFamily::parse("Palatino Linotype"), Some(Named(Cow::Borrowed("Palatino Linotype")))); - /// assert_eq!(FontFamily::parse("monospace"), Some(Generic(Monospace))); - /// - /// // Note that you can quote a generic family to capture it as a named family: - /// - /// assert_eq!(FontFamily::parse("'monospace'"), Some(Named(Cow::Borrowed("monospace")))); - /// ``` - pub fn parse(s: &'a str) -> Option { - Self::parse_list(s).next() - } - - /// Parses a comma separated list of font families. - /// - /// # Example - /// ``` - /// # extern crate alloc; - /// use alloc::borrow::Cow; - /// use parley::style::FontFamily::{self, *}; - /// use parley::style::GenericFamily::*; - /// - /// let source = "Arial, 'Times New Roman', serif"; - /// - /// let parsed_families = FontFamily::parse_list(source).collect::>(); - /// let families = vec![Named(Cow::Borrowed("Arial")), Named(Cow::Borrowed("Times New Roman")), Generic(Serif)]; - /// - /// assert_eq!(parsed_families, families); - /// ``` - pub fn parse_list(s: &'a str) -> impl Iterator> + 'a + Clone { - ParseList { - source: s.as_bytes(), - len: s.len(), - pos: 0, - } - } -} - -impl From for FontFamily<'_> { - fn from(f: GenericFamily) -> Self { - FontFamily::Generic(f) - } -} - impl From for FontStack<'_> { fn from(f: GenericFamily) -> Self { FontStack::Single(f.into()) @@ -113,87 +50,6 @@ impl<'a> From<&'a [FontFamily<'a>]> for FontStack<'a> { } } -impl fmt::Display for FontFamily<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Named(name) => write!(f, "{:?}", name), - Self::Generic(family) => write!(f, "{}", family), - } - } -} - -#[derive(Clone)] -struct ParseList<'a> { - source: &'a [u8], - len: usize, - pos: usize, -} - -impl<'a> Iterator for ParseList<'a> { - type Item = FontFamily<'a>; - - fn next(&mut self) -> Option { - let mut quote = None; - let mut pos = self.pos; - while pos < self.len && { - let ch = self.source[pos]; - ch.is_ascii_whitespace() || ch == b',' - } { - pos += 1; - } - self.pos = pos; - if pos >= self.len { - return None; - } - let first = self.source[pos]; - let mut start = pos; - match first { - b'"' | b'\'' => { - quote = Some(first); - pos += 1; - start += 1; - } - _ => {} - } - if let Some(quote) = quote { - while pos < self.len { - if self.source[pos] == quote { - self.pos = pos + 1; - return Some(FontFamily::Named(Cow::Borrowed( - core::str::from_utf8(self.source.get(start..pos)?) - .ok()? - .trim(), - ))); - } - pos += 1; - } - self.pos = pos; - return Some(FontFamily::Named(Cow::Borrowed( - core::str::from_utf8(self.source.get(start..pos)?) - .ok()? - .trim(), - ))); - } - let mut end = start; - while pos < self.len { - if self.source[pos] == b',' { - pos += 1; - break; - } - pos += 1; - end += 1; - } - self.pos = pos; - let name = core::str::from_utf8(self.source.get(start..end)?) - .ok()? - .trim(); - Some(match GenericFamily::parse(name) { - Some(family) => FontFamily::Generic(family), - _ => FontFamily::Named(Cow::Borrowed(name)), - }) - } -} - /// Font settings that can be supplied as a raw source string or /// a parsed slice. #[derive(Clone, PartialEq, Debug)] diff --git a/styled_text/src/font_family.rs b/styled_text/src/font_family.rs new file mode 100644 index 00000000..7a5b924b --- /dev/null +++ b/styled_text/src/font_family.rs @@ -0,0 +1,151 @@ +// Copyright 2021 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use alloc::borrow::Cow; +use core::fmt; + +use crate::GenericFamily; + +/// Named or generic font family. +/// +/// +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum FontFamily<'a> { + /// Named font family. + Named(Cow<'a, str>), + /// Generic font family. + Generic(GenericFamily), +} + +impl<'a> FontFamily<'a> { + /// Parses a font family containing a name or a generic family. + /// + /// # Example + /// ``` + /// # extern crate alloc; + /// use alloc::borrow::Cow; + /// use styled_text::FontFamily::{self, *}; + /// use styled_text::GenericFamily::*; + /// + /// assert_eq!(FontFamily::parse("Palatino Linotype"), Some(Named(Cow::Borrowed("Palatino Linotype")))); + /// assert_eq!(FontFamily::parse("monospace"), Some(Generic(Monospace))); + /// + /// // Note that you can quote a generic family to capture it as a named family: + /// + /// assert_eq!(FontFamily::parse("'monospace'"), Some(Named(Cow::Borrowed("monospace")))); + /// ``` + pub fn parse(s: &'a str) -> Option { + Self::parse_list(s).next() + } + + /// Parses a comma separated list of font families. + /// + /// # Example + /// ``` + /// # extern crate alloc; + /// use alloc::borrow::Cow; + /// use styled_text::FontFamily::{self, *}; + /// use styled_text::GenericFamily::*; + /// + /// let source = "Arial, 'Times New Roman', serif"; + /// + /// let parsed_families = FontFamily::parse_list(source).collect::>(); + /// let families = vec![Named(Cow::Borrowed("Arial")), Named(Cow::Borrowed("Times New Roman")), Generic(Serif)]; + /// + /// assert_eq!(parsed_families, families); + /// ``` + pub fn parse_list(s: &'a str) -> impl Iterator> + 'a + Clone { + ParseList { + source: s.as_bytes(), + len: s.len(), + pos: 0, + } + } +} + +impl From for FontFamily<'_> { + fn from(f: GenericFamily) -> Self { + FontFamily::Generic(f) + } +} + +impl fmt::Display for FontFamily<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Named(name) => write!(f, "{:?}", name), + Self::Generic(family) => write!(f, "{}", family), + } + } +} + +#[derive(Clone)] +struct ParseList<'a> { + source: &'a [u8], + len: usize, + pos: usize, +} + +impl<'a> Iterator for ParseList<'a> { + type Item = FontFamily<'a>; + + fn next(&mut self) -> Option { + let mut quote = None; + let mut pos = self.pos; + while pos < self.len && { + let ch = self.source[pos]; + ch.is_ascii_whitespace() || ch == b',' + } { + pos += 1; + } + self.pos = pos; + if pos >= self.len { + return None; + } + let first = self.source[pos]; + let mut start = pos; + match first { + b'"' | b'\'' => { + quote = Some(first); + pos += 1; + start += 1; + } + _ => {} + } + if let Some(quote) = quote { + while pos < self.len { + if self.source[pos] == quote { + self.pos = pos + 1; + return Some(FontFamily::Named(Cow::Borrowed( + core::str::from_utf8(self.source.get(start..pos)?) + .ok()? + .trim(), + ))); + } + pos += 1; + } + self.pos = pos; + return Some(FontFamily::Named(Cow::Borrowed( + core::str::from_utf8(self.source.get(start..pos)?) + .ok()? + .trim(), + ))); + } + let mut end = start; + while pos < self.len { + if self.source[pos] == b',' { + pos += 1; + break; + } + pos += 1; + end += 1; + } + self.pos = pos; + let name = core::str::from_utf8(self.source.get(start..end)?) + .ok()? + .trim(); + Some(match GenericFamily::parse(name) { + Some(family) => FontFamily::Generic(family), + _ => FontFamily::Named(Cow::Borrowed(name)), + }) + } +} diff --git a/styled_text/src/lib.rs b/styled_text/src/lib.rs index f1633791..e50c10a3 100644 --- a/styled_text/src/lib.rs +++ b/styled_text/src/lib.rs @@ -16,8 +16,15 @@ #![allow(clippy::exhaustive_enums)] #![allow(clippy::use_self)] +#[cfg(not(any(feature = "std", feature = "libm")))] +compile_error!("parley requires either the `std` or `libm` feature to be enabled"); + +extern crate alloc; + mod attributes; +mod font_family; mod generic; pub use attributes::{Stretch, Style, Weight}; +pub use font_family::FontFamily; pub use generic::GenericFamily; From 7699e03231d695d7549bca50f08719788716e28c Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sat, 7 Dec 2024 23:56:09 +0700 Subject: [PATCH 04/16] Move `FontSettings`, `FontStack` to `styled_text` --- parley/src/style/font.rs | 80 ++------------------------------ styled_text/src/font_settings.rs | 40 ++++++++++++++++ styled_text/src/font_stack.rs | 43 +++++++++++++++++ styled_text/src/lib.rs | 4 ++ 4 files changed, 91 insertions(+), 76 deletions(-) create mode 100644 styled_text/src/font_settings.rs create mode 100644 styled_text/src/font_stack.rs diff --git a/parley/src/style/font.rs b/parley/src/style/font.rs index a890e448..f74a935f 100644 --- a/parley/src/style/font.rs +++ b/parley/src/style/font.rs @@ -1,85 +1,13 @@ // Copyright 2021 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use alloc::borrow::Cow; -use alloc::borrow::ToOwned; -use core::fmt; - -pub use styled_text::{FontFamily, GenericFamily, Stretch as FontStretch, Style as FontStyle, Weight as FontWeight}; +pub use styled_text::{ + FontFamily, FontSettings, FontStack, GenericFamily, Stretch as FontStretch, Style as FontStyle, + Weight as FontWeight, +}; /// Setting for a font variation. pub type FontVariation = swash::Setting; /// Setting for a font feature. pub type FontFeature = swash::Setting; - -/// Prioritized sequence of font families. -/// -/// -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum FontStack<'a> { - /// Font family list in CSS format. - Source(Cow<'a, str>), - /// Single font family. - Single(FontFamily<'a>), - /// Ordered list of font families. - List(Cow<'a, [FontFamily<'a>]>), -} - -impl From for FontStack<'_> { - fn from(f: GenericFamily) -> Self { - FontStack::Single(f.into()) - } -} - -impl<'a> From> for FontStack<'a> { - fn from(f: FontFamily<'a>) -> Self { - FontStack::Single(f) - } -} - -impl<'a> From<&'a str> for FontStack<'a> { - fn from(s: &'a str) -> Self { - FontStack::Source(Cow::Borrowed(s)) - } -} - -impl<'a> From<&'a [FontFamily<'a>]> for FontStack<'a> { - fn from(fs: &'a [FontFamily<'a>]) -> Self { - FontStack::List(Cow::Borrowed(fs)) - } -} - -/// Font settings that can be supplied as a raw source string or -/// a parsed slice. -#[derive(Clone, PartialEq, Debug)] -pub enum FontSettings<'a, T> -where - [T]: ToOwned, - <[T] as ToOwned>::Owned: fmt::Debug + PartialEq + Clone, -{ - /// Setting source in CSS format. - Source(Cow<'a, str>), - /// List of settings. - List(Cow<'a, [T]>), -} - -impl<'a, T> From<&'a str> for FontSettings<'a, T> -where - [T]: ToOwned, - <[T] as ToOwned>::Owned: fmt::Debug + PartialEq + Clone, -{ - fn from(value: &'a str) -> Self { - Self::Source(Cow::Borrowed(value)) - } -} - -impl<'a, T> From<&'a [T]> for FontSettings<'a, T> -where - [T]: ToOwned, - <[T] as ToOwned>::Owned: fmt::Debug + PartialEq + Clone, -{ - fn from(value: &'a [T]) -> Self { - Self::List(Cow::Borrowed(value)) - } -} diff --git a/styled_text/src/font_settings.rs b/styled_text/src/font_settings.rs new file mode 100644 index 00000000..87c36629 --- /dev/null +++ b/styled_text/src/font_settings.rs @@ -0,0 +1,40 @@ +// Copyright 2021 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use alloc::borrow::Cow; +use alloc::borrow::ToOwned; +use core::fmt; + +/// Font settings that can be supplied as a raw source string or +/// a parsed slice. +#[derive(Clone, PartialEq, Debug)] +pub enum FontSettings<'a, T> +where + [T]: ToOwned, + <[T] as ToOwned>::Owned: fmt::Debug + PartialEq + Clone, +{ + /// Setting source in CSS format. + Source(Cow<'a, str>), + /// List of settings. + List(Cow<'a, [T]>), +} + +impl<'a, T> From<&'a str> for FontSettings<'a, T> +where + [T]: ToOwned, + <[T] as ToOwned>::Owned: fmt::Debug + PartialEq + Clone, +{ + fn from(value: &'a str) -> Self { + Self::Source(Cow::Borrowed(value)) + } +} + +impl<'a, T> From<&'a [T]> for FontSettings<'a, T> +where + [T]: ToOwned, + <[T] as ToOwned>::Owned: fmt::Debug + PartialEq + Clone, +{ + fn from(value: &'a [T]) -> Self { + Self::List(Cow::Borrowed(value)) + } +} diff --git a/styled_text/src/font_stack.rs b/styled_text/src/font_stack.rs new file mode 100644 index 00000000..100793ba --- /dev/null +++ b/styled_text/src/font_stack.rs @@ -0,0 +1,43 @@ +// Copyright 2021 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use alloc::borrow::Cow; + +use crate::{FontFamily, GenericFamily}; + +/// Prioritized sequence of font families. +/// +/// +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum FontStack<'a> { + /// Font family list in CSS format. + Source(Cow<'a, str>), + /// Single font family. + Single(FontFamily<'a>), + /// Ordered list of font families. + List(Cow<'a, [FontFamily<'a>]>), +} + +impl From for FontStack<'_> { + fn from(f: GenericFamily) -> Self { + FontStack::Single(f.into()) + } +} + +impl<'a> From> for FontStack<'a> { + fn from(f: FontFamily<'a>) -> Self { + FontStack::Single(f) + } +} + +impl<'a> From<&'a str> for FontStack<'a> { + fn from(s: &'a str) -> Self { + FontStack::Source(Cow::Borrowed(s)) + } +} + +impl<'a> From<&'a [FontFamily<'a>]> for FontStack<'a> { + fn from(fs: &'a [FontFamily<'a>]) -> Self { + FontStack::List(Cow::Borrowed(fs)) + } +} diff --git a/styled_text/src/lib.rs b/styled_text/src/lib.rs index e50c10a3..2b83aa19 100644 --- a/styled_text/src/lib.rs +++ b/styled_text/src/lib.rs @@ -23,8 +23,12 @@ extern crate alloc; mod attributes; mod font_family; +mod font_settings; +mod font_stack; mod generic; pub use attributes::{Stretch, Style, Weight}; pub use font_family::FontFamily; +pub use font_settings::FontSettings; +pub use font_stack::FontStack; pub use generic::GenericFamily; From c0dda67c5d466ab5c6b3ad24d111c4d38dd740d2 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sun, 8 Dec 2024 00:06:23 +0700 Subject: [PATCH 05/16] Move `Brush` to `styled_text` --- parley/src/style/mod.rs | 3 +-- {parley/src/style => styled_text/src}/brush.rs | 0 styled_text/src/lib.rs | 2 ++ 3 files changed, 3 insertions(+), 2 deletions(-) rename {parley/src/style => styled_text/src}/brush.rs (100%) diff --git a/parley/src/style/mod.rs b/parley/src/style/mod.rs index c99382aa..1f047255 100644 --- a/parley/src/style/mod.rs +++ b/parley/src/style/mod.rs @@ -3,13 +3,12 @@ //! Rich styling support. -mod brush; mod font; mod styleset; use alloc::borrow::Cow; +pub use styled_text::Brush; -pub use brush::*; pub use font::{ FontFamily, FontFeature, FontSettings, FontStack, FontStretch, FontStyle, FontVariation, FontWeight, GenericFamily, diff --git a/parley/src/style/brush.rs b/styled_text/src/brush.rs similarity index 100% rename from parley/src/style/brush.rs rename to styled_text/src/brush.rs diff --git a/styled_text/src/lib.rs b/styled_text/src/lib.rs index 2b83aa19..5f27ad56 100644 --- a/styled_text/src/lib.rs +++ b/styled_text/src/lib.rs @@ -22,12 +22,14 @@ compile_error!("parley requires either the `std` or `libm` feature to be enabled extern crate alloc; mod attributes; +mod brush; mod font_family; mod font_settings; mod font_stack; mod generic; pub use attributes::{Stretch, Style, Weight}; +pub use brush::Brush; pub use font_family::FontFamily; pub use font_settings::FontSettings; pub use font_stack::FontStack; From 365c086ea88a08292b7bb32c91a175aa5cc0f456 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sun, 8 Dec 2024 00:23:52 +0700 Subject: [PATCH 06/16] Finish moving style/font.rs stuff into `styled_text` --- Cargo.lock | 1 + parley/src/style/font.rs | 13 ------------- parley/src/style/mod.rs | 10 ++++------ styled_text/Cargo.toml | 1 + styled_text/src/font_settings.rs | 8 ++++++++ styled_text/src/lib.rs | 2 +- 6 files changed, 15 insertions(+), 20 deletions(-) delete mode 100644 parley/src/style/font.rs diff --git a/Cargo.lock b/Cargo.lock index 8454440a..5c97a63e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2457,6 +2457,7 @@ name = "styled_text" version = "0.1.0" dependencies = [ "core_maths", + "swash", ] [[package]] diff --git a/parley/src/style/font.rs b/parley/src/style/font.rs deleted file mode 100644 index f74a935f..00000000 --- a/parley/src/style/font.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2021 the Parley Authors -// SPDX-License-Identifier: Apache-2.0 OR MIT - -pub use styled_text::{ - FontFamily, FontSettings, FontStack, GenericFamily, Stretch as FontStretch, Style as FontStyle, - Weight as FontWeight, -}; - -/// Setting for a font variation. -pub type FontVariation = swash::Setting; - -/// Setting for a font feature. -pub type FontFeature = swash::Setting; diff --git a/parley/src/style/mod.rs b/parley/src/style/mod.rs index 1f047255..3f6c8207 100644 --- a/parley/src/style/mod.rs +++ b/parley/src/style/mod.rs @@ -3,16 +3,14 @@ //! Rich styling support. -mod font; mod styleset; use alloc::borrow::Cow; -pub use styled_text::Brush; - -pub use font::{ - FontFamily, FontFeature, FontSettings, FontStack, FontStretch, FontStyle, FontVariation, - FontWeight, GenericFamily, +pub use styled_text::{ + Brush, FontFamily, FontFeature, FontSettings, FontStack, FontVariation, GenericFamily, + Stretch as FontStretch, Style as FontStyle, Weight as FontWeight, }; + pub use styleset::StyleSet; #[derive(Debug, Clone, Copy)] diff --git a/styled_text/Cargo.toml b/styled_text/Cargo.toml index d9df4915..636025eb 100644 --- a/styled_text/Cargo.toml +++ b/styled_text/Cargo.toml @@ -21,3 +21,4 @@ libm = ["dep:core_maths"] [dependencies] core_maths = { version = "0.1.0", optional = true } +swash = { workspace = true } diff --git a/styled_text/src/font_settings.rs b/styled_text/src/font_settings.rs index 87c36629..a60995fd 100644 --- a/styled_text/src/font_settings.rs +++ b/styled_text/src/font_settings.rs @@ -5,6 +5,14 @@ use alloc::borrow::Cow; use alloc::borrow::ToOwned; use core::fmt; +/// Setting for a font variation. +// FIXME(style): We should copy the Setting definition from swash instead of having a dep +pub type FontVariation = swash::Setting; + +/// Setting for a font feature. +// FIXME(style): We should copy the Setting definition from swash instead of having a dep +pub type FontFeature = swash::Setting; + /// Font settings that can be supplied as a raw source string or /// a parsed slice. #[derive(Clone, PartialEq, Debug)] diff --git a/styled_text/src/lib.rs b/styled_text/src/lib.rs index 5f27ad56..11cdbcf8 100644 --- a/styled_text/src/lib.rs +++ b/styled_text/src/lib.rs @@ -31,6 +31,6 @@ mod generic; pub use attributes::{Stretch, Style, Weight}; pub use brush::Brush; pub use font_family::FontFamily; -pub use font_settings::FontSettings; +pub use font_settings::{FontFeature, FontSettings, FontVariation}; pub use font_stack::FontStack; pub use generic::GenericFamily; From 1882226ad2f24fe9e7fb2c440463ebe074632ce1 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sun, 8 Dec 2024 00:48:45 +0700 Subject: [PATCH 07/16] Move `StyleProperty` to `styled_text` --- parley/src/style/mod.rs | 71 +---------------------------- styled_text/src/lib.rs | 2 + styled_text/src/style_property.rs | 76 +++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 70 deletions(-) create mode 100644 styled_text/src/style_property.rs diff --git a/parley/src/style/mod.rs b/parley/src/style/mod.rs index 3f6c8207..059b7db1 100644 --- a/parley/src/style/mod.rs +++ b/parley/src/style/mod.rs @@ -8,7 +8,7 @@ mod styleset; use alloc::borrow::Cow; pub use styled_text::{ Brush, FontFamily, FontFeature, FontSettings, FontStack, FontVariation, GenericFamily, - Stretch as FontStretch, Style as FontStyle, Weight as FontWeight, + Stretch as FontStretch, Style as FontStyle, StyleProperty, Weight as FontWeight, }; pub use styleset::StyleSet; @@ -19,51 +19,6 @@ pub enum WhiteSpaceCollapse { Preserve, } -/// Properties that define a style. -#[derive(Clone, PartialEq, Debug)] -pub enum StyleProperty<'a, B: Brush> { - /// Font family stack. - FontStack(FontStack<'a>), - /// Font size. - FontSize(f32), - /// Font stretch. - FontStretch(FontStretch), - /// Font style. - FontStyle(FontStyle), - /// Font weight. - FontWeight(FontWeight), - /// Font variation settings. - FontVariations(FontSettings<'a, FontVariation>), - /// Font feature settings. - FontFeatures(FontSettings<'a, FontFeature>), - /// Locale. - Locale(Option<&'a str>), - /// Brush for rendering text. - Brush(B), - /// Underline decoration. - Underline(bool), - /// Offset of the underline decoration. - UnderlineOffset(Option), - /// Size of the underline decoration. - UnderlineSize(Option), - /// Brush for rendering the underline decoration. - UnderlineBrush(Option), - /// Strikethrough decoration. - Strikethrough(bool), - /// Offset of the strikethrough decoration. - StrikethroughOffset(Option), - /// Size of the strikethrough decoration. - StrikethroughSize(Option), - /// Brush for rendering the strikethrough decoration. - StrikethroughBrush(Option), - /// Line height multiplier. - LineHeight(f32), - /// Extra spacing between words. - WordSpacing(f32), - /// Extra spacing between letters. - LetterSpacing(f32), -} - /// Unresolved styles. #[derive(Clone, PartialEq, Debug)] pub struct TextStyle<'a, B: Brush> { @@ -135,27 +90,3 @@ impl Default for TextStyle<'_, B> { } } } - -impl<'a, B: Brush> From> for StyleProperty<'a, B> { - fn from(fs: FontStack<'a>) -> Self { - StyleProperty::FontStack(fs) - } -} - -impl<'a, B: Brush> From<&'a [FontFamily<'a>]> for StyleProperty<'a, B> { - fn from(fs: &'a [FontFamily<'a>]) -> Self { - StyleProperty::FontStack(fs.into()) - } -} - -impl<'a, B: Brush> From> for StyleProperty<'a, B> { - fn from(f: FontFamily<'a>) -> Self { - StyleProperty::FontStack(FontStack::from(f)) - } -} - -impl From for StyleProperty<'_, B> { - fn from(f: GenericFamily) -> Self { - StyleProperty::FontStack(f.into()) - } -} diff --git a/styled_text/src/lib.rs b/styled_text/src/lib.rs index 11cdbcf8..f122e761 100644 --- a/styled_text/src/lib.rs +++ b/styled_text/src/lib.rs @@ -27,6 +27,7 @@ mod font_family; mod font_settings; mod font_stack; mod generic; +mod style_property; pub use attributes::{Stretch, Style, Weight}; pub use brush::Brush; @@ -34,3 +35,4 @@ pub use font_family::FontFamily; pub use font_settings::{FontFeature, FontSettings, FontVariation}; pub use font_stack::FontStack; pub use generic::GenericFamily; +pub use style_property::StyleProperty; diff --git a/styled_text/src/style_property.rs b/styled_text/src/style_property.rs new file mode 100644 index 00000000..19a70b15 --- /dev/null +++ b/styled_text/src/style_property.rs @@ -0,0 +1,76 @@ +// Copyright 2021 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use crate::{ + Brush, FontFamily, FontFeature, FontSettings, FontStack, FontVariation, GenericFamily, + Stretch as FontStretch, Style as FontStyle, Weight as FontWeight, +}; + +/// Properties that define a style. +#[derive(Clone, PartialEq, Debug)] +pub enum StyleProperty<'a, B: Brush> { + /// Font family stack. + FontStack(FontStack<'a>), + /// Font size. + FontSize(f32), + /// Font stretch. + FontStretch(FontStretch), + /// Font style. + FontStyle(FontStyle), + /// Font weight. + FontWeight(FontWeight), + /// Font variation settings. + FontVariations(FontSettings<'a, FontVariation>), + /// Font feature settings. + FontFeatures(FontSettings<'a, FontFeature>), + /// Locale. + Locale(Option<&'a str>), + /// Brush for rendering text. + Brush(B), + /// Underline decoration. + Underline(bool), + /// Offset of the underline decoration. + UnderlineOffset(Option), + /// Size of the underline decoration. + UnderlineSize(Option), + /// Brush for rendering the underline decoration. + UnderlineBrush(Option), + /// Strikethrough decoration. + Strikethrough(bool), + /// Offset of the strikethrough decoration. + StrikethroughOffset(Option), + /// Size of the strikethrough decoration. + StrikethroughSize(Option), + /// Brush for rendering the strikethrough decoration. + StrikethroughBrush(Option), + /// Line height multiplier. + LineHeight(f32), + /// Extra spacing between words. + WordSpacing(f32), + /// Extra spacing between letters. + LetterSpacing(f32), +} + +impl<'a, B: Brush> From> for StyleProperty<'a, B> { + fn from(fs: FontStack<'a>) -> Self { + StyleProperty::FontStack(fs) + } +} + +impl<'a, B: Brush> From<&'a [FontFamily<'a>]> for StyleProperty<'a, B> { + fn from(fs: &'a [FontFamily<'a>]) -> Self { + StyleProperty::FontStack(fs.into()) + } +} + +impl<'a, B: Brush> From> for StyleProperty<'a, B> { + fn from(f: FontFamily<'a>) -> Self { + StyleProperty::FontStack(FontStack::from(f)) + } +} + +impl From for StyleProperty<'_, B> { + fn from(f: GenericFamily) -> Self { + StyleProperty::FontStack(f.into()) + } +} From 40819b7ceb4a0d025ba157c24ffafe98ac16053e Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sun, 8 Dec 2024 01:21:27 +0700 Subject: [PATCH 08/16] Vendor `Setting` and `Tag` into `styled_text` This lets us remove the `swash` dependency from `styled_text`. --- Cargo.lock | 1 - parley/src/resolve/mod.rs | 13 +- parley/src/style/mod.rs | 14 +- styled_text/Cargo.toml | 1 - styled_text/src/font_settings.rs | 4 +- styled_text/src/lib.rs | 1 + styled_text/src/setting.rs | 223 +++++++++++++++++++++++++++++++ 7 files changed, 245 insertions(+), 12 deletions(-) create mode 100644 styled_text/src/setting.rs diff --git a/Cargo.lock b/Cargo.lock index 5c97a63e..8454440a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2457,7 +2457,6 @@ name = "styled_text" version = "0.1.0" dependencies = [ "core_maths", - "swash", ] [[package]] diff --git a/parley/src/resolve/mod.rs b/parley/src/resolve/mod.rs index f1c5492f..f0cf6834 100644 --- a/parley/src/resolve/mod.rs +++ b/parley/src/resolve/mod.rs @@ -251,7 +251,7 @@ impl ResolveContext { /// Resolves font variation settings. pub(crate) fn resolve_variations( &mut self, - variations: &FontSettings, + variations: &FontSettings, ) -> Resolved { match variations { FontSettings::Source(source) => { @@ -261,7 +261,11 @@ impl ResolveContext { } FontSettings::List(settings) => { self.tmp_variations.clear(); - self.tmp_variations.extend_from_slice(settings); + self.tmp_variations.extend( + settings + .iter() + .map(|v| FontVariation::from((v.tag, v.value))), + ); } } if self.tmp_variations.is_empty() { @@ -276,7 +280,7 @@ impl ResolveContext { /// Resolves font feature settings. pub(crate) fn resolve_features( &mut self, - features: &FontSettings, + features: &FontSettings, ) -> Resolved { match features { FontSettings::Source(source) => { @@ -285,7 +289,8 @@ impl ResolveContext { } FontSettings::List(settings) => { self.tmp_features.clear(); - self.tmp_features.extend_from_slice(settings); + self.tmp_features + .extend(settings.iter().map(|f| FontFeature::from((f.tag, f.value)))); } } if self.tmp_features.is_empty() { diff --git a/parley/src/style/mod.rs b/parley/src/style/mod.rs index 059b7db1..5717f76e 100644 --- a/parley/src/style/mod.rs +++ b/parley/src/style/mod.rs @@ -7,12 +7,18 @@ mod styleset; use alloc::borrow::Cow; pub use styled_text::{ - Brush, FontFamily, FontFeature, FontSettings, FontStack, FontVariation, GenericFamily, - Stretch as FontStretch, Style as FontStyle, StyleProperty, Weight as FontWeight, + Brush, FontFamily, FontSettings, FontStack, GenericFamily, Stretch as FontStretch, + Style as FontStyle, StyleProperty, Weight as FontWeight, }; pub use styleset::StyleSet; +/// Setting for a font variation. +pub type FontVariation = swash::Setting; + +/// Setting for a font feature. +pub type FontFeature = swash::Setting; + #[derive(Debug, Clone, Copy)] pub enum WhiteSpaceCollapse { Collapse, @@ -33,9 +39,9 @@ pub struct TextStyle<'a, B: Brush> { /// Font weight. pub font_weight: FontWeight, /// Font variation settings. - pub font_variations: FontSettings<'a, FontVariation>, + pub font_variations: FontSettings<'a, styled_text::FontVariation>, /// Font feature settings. - pub font_features: FontSettings<'a, FontFeature>, + pub font_features: FontSettings<'a, styled_text::FontFeature>, /// Locale. pub locale: Option<&'a str>, /// Brush for rendering text. diff --git a/styled_text/Cargo.toml b/styled_text/Cargo.toml index 636025eb..d9df4915 100644 --- a/styled_text/Cargo.toml +++ b/styled_text/Cargo.toml @@ -21,4 +21,3 @@ libm = ["dep:core_maths"] [dependencies] core_maths = { version = "0.1.0", optional = true } -swash = { workspace = true } diff --git a/styled_text/src/font_settings.rs b/styled_text/src/font_settings.rs index a60995fd..433ef47d 100644 --- a/styled_text/src/font_settings.rs +++ b/styled_text/src/font_settings.rs @@ -7,11 +7,11 @@ use core::fmt; /// Setting for a font variation. // FIXME(style): We should copy the Setting definition from swash instead of having a dep -pub type FontVariation = swash::Setting; +pub type FontVariation = crate::setting::Setting; /// Setting for a font feature. // FIXME(style): We should copy the Setting definition from swash instead of having a dep -pub type FontFeature = swash::Setting; +pub type FontFeature = crate::setting::Setting; /// Font settings that can be supplied as a raw source string or /// a parsed slice. diff --git a/styled_text/src/lib.rs b/styled_text/src/lib.rs index f122e761..612a3303 100644 --- a/styled_text/src/lib.rs +++ b/styled_text/src/lib.rs @@ -27,6 +27,7 @@ mod font_family; mod font_settings; mod font_stack; mod generic; +pub mod setting; mod style_property; pub use attributes::{Stretch, Style, Weight}; diff --git a/styled_text/src/setting.rs b/styled_text/src/setting.rs new file mode 100644 index 00000000..0c1cef47 --- /dev/null +++ b/styled_text/src/setting.rs @@ -0,0 +1,223 @@ +// Copyright 2024 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use core::fmt; + +/// Four byte tag value. +pub type Tag = u32; + +/// Creates a tag from four bytes. +pub const fn tag_from_bytes(bytes: &[u8; 4]) -> Tag { + (bytes[0] as u32) << 24 | (bytes[1] as u32) << 16 | (bytes[2] as u32) << 8 | bytes[3] as u32 +} + +/// Creates a tag from the first four bytes of a string, inserting +/// spaces for any missing bytes. +pub fn tag_from_str_lossy(s: &str) -> Tag { + let mut bytes = [b' '; 4]; + for (i, b) in s.as_bytes().iter().enumerate().take(4) { + bytes[i] = *b; + } + tag_from_bytes(&bytes) +} + +/// Setting combining a tag and a value for features and variations. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +pub struct Setting { + /// The tag that identifies the setting. + pub tag: Tag, + /// The value for the setting. + pub value: T, +} + +impl fmt::Display for Setting { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let bytes = self.tag.to_be_bytes(); + let tag_name = core::str::from_utf8(&bytes).unwrap_or(""); + write!(f, "\"{}\" {}", tag_name, self.value) + } +} + +impl Setting { + /// Parses a feature setting according to the CSS grammar. + pub fn parse(s: &str) -> Option { + Self::parse_list(s).next() + } + + /// Parses a comma separated list of feature settings according to the CSS + /// grammar. + pub fn parse_list(s: &str) -> impl Iterator + '_ + Clone { + ParseList::new(s) + .map(|(_, tag, value_str)| { + let (ok, value) = match value_str { + "on" | "" => (true, 1), + "off" => (true, 0), + _ => match value_str.parse::() { + Ok(value) => (true, value), + _ => (false, 0), + }, + }; + (ok, tag, value) + }) + .take_while(|(ok, _, _)| *ok) + .map(|(_, tag, value)| Self { tag, value }) + } +} + +impl Setting { + /// Parses a variation setting according to the CSS grammar. + pub fn parse(s: &str) -> Option { + Self::parse_list(s).next() + } + + /// Parses a comma separated list of variation settings according to the + /// CSS grammar. + pub fn parse_list(s: &str) -> impl Iterator + '_ + Clone { + ParseList::new(s) + .map(|(_, tag, value_str)| { + let (ok, value) = match value_str.parse::() { + Ok(value) => (true, value), + _ => (false, 0.), + }; + (ok, tag, value) + }) + .take_while(|(ok, _, _)| *ok) + .map(|(_, tag, value)| Self { tag, value }) + } +} + +impl From<(Tag, T)> for Setting { + fn from(v: (Tag, T)) -> Self { + Self { + tag: v.0, + value: v.1, + } + } +} + +impl From<&(Tag, T)> for Setting { + fn from(v: &(Tag, T)) -> Self { + Self { + tag: v.0, + value: v.1, + } + } +} + +impl From<&([u8; 4], T)> for Setting { + fn from(v: &([u8; 4], T)) -> Self { + Self { + tag: tag_from_bytes(&v.0), + value: v.1, + } + } +} + +impl From<&(&[u8; 4], T)> for Setting { + fn from(v: &(&[u8; 4], T)) -> Self { + Self { + tag: tag_from_bytes(v.0), + value: v.1, + } + } +} + +impl From<(&str, T)> for Setting { + fn from(v: (&str, T)) -> Self { + Self { + tag: tag_from_str_lossy(v.0), + value: v.1, + } + } +} + +impl From<&(&str, T)> for Setting { + fn from(v: &(&str, T)) -> Self { + Self { + tag: tag_from_str_lossy(v.0), + value: v.1, + } + } +} + +#[derive(Clone)] +struct ParseList<'a> { + source: &'a [u8], + len: usize, + pos: usize, +} + +impl<'a> ParseList<'a> { + fn new(source: &'a str) -> Self { + Self { + source: source.as_bytes(), + len: source.len(), + pos: 0, + } + } +} + +impl<'a> Iterator for ParseList<'a> { + type Item = (usize, Tag, &'a str); + + fn next(&mut self) -> Option { + let mut pos = self.pos; + while pos < self.len && { + let ch = self.source[pos]; + ch.is_ascii_whitespace() || ch == b',' + } { + pos += 1; + } + self.pos = pos; + if pos >= self.len { + return None; + } + let first = self.source[pos]; + let mut start = pos; + let quote = match first { + b'"' | b'\'' => { + pos += 1; + start += 1; + first + } + _ => return None, + }; + let mut tag_str = None; + while pos < self.len { + if self.source[pos] == quote { + tag_str = core::str::from_utf8(self.source.get(start..pos)?).ok(); + pos += 1; + break; + } + pos += 1; + } + self.pos = pos; + let tag_str = tag_str?; + if tag_str.len() != 4 || !tag_str.is_ascii() { + return None; + } + let tag = tag_from_str_lossy(tag_str); + while pos < self.len { + if !self.source[pos].is_ascii_whitespace() { + break; + } + pos += 1; + } + self.pos = pos; + start = pos; + let mut end = start; + while pos < self.len { + if self.source[pos] == b',' { + pos += 1; + break; + } + pos += 1; + end += 1; + } + let value = core::str::from_utf8(self.source.get(start..end)?) + .ok()? + .trim(); + self.pos = pos; + Some((pos, tag, value)) + } +} From a13aa93d9b3ed06624a5250c41360db1f64a800a Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sun, 8 Dec 2024 10:00:53 +0700 Subject: [PATCH 09/16] Fix fontique's fontconfig conversion code. --- fontique/src/backend/fontconfig/cache.rs | 97 ++++++++++++------------ 1 file changed, 47 insertions(+), 50 deletions(-) diff --git a/fontique/src/backend/fontconfig/cache.rs b/fontique/src/backend/fontconfig/cache.rs index bb1f753c..cf60e474 100644 --- a/fontique/src/backend/fontconfig/cache.rs +++ b/fontique/src/backend/fontconfig/cache.rs @@ -1,65 +1,62 @@ // Copyright 2024 the Parley Authors // SPDX-License-Identifier: Apache-2.0 OR MIT -use super::{Stretch, Style, Weight}; use fontconfig_cache_parser::{Cache, CharSetLeaf, Object, Pattern, Value}; use std::io::Read; use std::path::PathBuf; +use styled_text::{Stretch, Style, Weight}; -impl Stretch { - fn from_fc(width: i32) -> Self { - match width { - 63 => Self::EXTRA_CONDENSED, - 87 => Self::SEMI_CONDENSED, - 113 => Self::SEMI_EXPANDED, - _ => Self::from_ratio(width as f32 / 100.0), - } +// FIXME(style): There's an argument for putting this into styled_text +fn stretch_from_fc(width: i32) -> Stretch { + match width { + 63 => Stretch::EXTRA_CONDENSED, + 87 => Stretch::SEMI_CONDENSED, + 113 => Stretch::SEMI_EXPANDED, + _ => Stretch::from_ratio(width as f32 / 100.0), } } -impl Style { - fn from_fc(slant: i32) -> Self { - match slant { - 100 => Self::Italic, - 110 => Self::Oblique(None), - _ => Self::Normal, - } +// FIXME(style): There's an argument for putting this into styled_text +fn style_from_fc(slant: i32) -> Style { + match slant { + 100 => Style::Italic, + 110 => Style::Oblique(None), + _ => Style::Normal, } } -impl Weight { - fn from_fc(weight: i32) -> Self { - const MAP: &[(i32, i32)] = &[ - (0, 0), - (100, 0), - (200, 40), - (300, 50), - (350, 55), - (380, 75), - (400, 80), - (500, 100), - (600, 180), - (700, 200), - (800, 205), - (900, 210), - (950, 215), - ]; - for (i, (ot, fc)) in MAP.iter().skip(1).enumerate() { - if weight == *fc { - return Self::new(*ot as f32); - } - if weight < *fc { - let weight = weight as f32; - let fc_a = MAP[i - 1].1 as f32; - let fc_b = *fc as f32; - let ot_a = MAP[i - 1].1 as f32; - let ot_b = *ot as f32; - let t = (fc_a - fc_b) / (weight - fc_a); - return Self::new(ot_a + (ot_b - ot_a) * t); - } +// FIXME(style): There's an argument for putting this into styled_text +fn weight_from_fc(weight: i32) -> Weight { + const MAP: &[(i32, i32)] = &[ + (0, 0), + (100, 0), + (200, 40), + (300, 50), + (350, 55), + (380, 75), + (400, 80), + (500, 100), + (600, 180), + (700, 200), + (800, 205), + (900, 210), + (950, 215), + ]; + for (i, (ot, fc)) in MAP.iter().skip(1).enumerate() { + if weight == *fc { + return Weight::new(*ot as f32); + } + if weight < *fc { + let weight = weight as f32; + let fc_a = MAP[i - 1].1 as f32; + let fc_b = *fc as f32; + let ot_a = MAP[i - 1].1 as f32; + let ot_b = *ot as f32; + let t = (fc_a - fc_b) / (weight - fc_a); + return Weight::new(ot_a + (ot_b - ot_a) * t); } - Weight::EXTRA_BLACK } + Weight::EXTRA_BLACK } #[derive(Default)] @@ -156,21 +153,21 @@ fn parse_font( Object::Slant => { for val in elt.values().ok()? { if let Value::Int(i) = val.ok()? { - font.style = Style::from_fc(i as _); + font.style = style_from_fc(i as _); } } } Object::Weight => { for val in elt.values().ok()? { if let Value::Int(i) = val.ok()? { - font.weight = Weight::from_fc(i as _); + font.weight = weight_from_fc(i as _); } } } Object::Width => { for val in elt.values().ok()? { if let Value::Int(i) = val.ok()? { - font.stretch = Stretch::from_fc(i as _); + font.stretch = stretch_from_fc(i as _); } } } From 79ab65fd8408909c2c377cfe22279ab424973c0b Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sun, 8 Dec 2024 10:17:07 +0700 Subject: [PATCH 10/16] Tidy up GenericFamily a bit. Rename the file in `styled_text` so it matches what it contains. Just re-export from fontique's root via styled_text rather than via fontique::generic. --- fontique/src/generic.rs | 4 +--- fontique/src/lib.rs | 3 +-- styled_text/src/{generic.rs => generic_family.rs} | 0 styled_text/src/lib.rs | 4 ++-- 4 files changed, 4 insertions(+), 7 deletions(-) rename styled_text/src/{generic.rs => generic_family.rs} (100%) diff --git a/fontique/src/generic.rs b/fontique/src/generic.rs index e48f1077..de6e19da 100644 --- a/fontique/src/generic.rs +++ b/fontique/src/generic.rs @@ -5,9 +5,7 @@ use super::FamilyId; use smallvec::SmallVec; - -// FIXME(style): Other users of this can import it from styled_text -pub use styled_text::GenericFamily; +use styled_text::GenericFamily; type FamilyVec = SmallVec<[FamilyId; 2]>; diff --git a/fontique/src/lib.rs b/fontique/src/lib.rs index 3753ee0f..709a6799 100644 --- a/fontique/src/lib.rs +++ b/fontique/src/lib.rs @@ -48,14 +48,13 @@ mod source_cache; pub use icu_locid::LanguageIdentifier as Language; pub use peniko::Blob; -pub use styled_text::{Stretch, Style, Weight}; +pub use styled_text::{GenericFamily, Stretch, Style, Weight}; pub use attributes::Attributes; pub use collection::{Collection, CollectionOptions, Query, QueryFamily, QueryFont, QueryStatus}; pub use fallback::FallbackKey; pub use family::{FamilyId, FamilyInfo}; pub use font::{AxisInfo, FontInfo, Synthesis}; -pub use generic::GenericFamily; pub use script::Script; pub use source::{SourceId, SourceInfo, SourceKind}; diff --git a/styled_text/src/generic.rs b/styled_text/src/generic_family.rs similarity index 100% rename from styled_text/src/generic.rs rename to styled_text/src/generic_family.rs diff --git a/styled_text/src/lib.rs b/styled_text/src/lib.rs index 612a3303..365b1cb7 100644 --- a/styled_text/src/lib.rs +++ b/styled_text/src/lib.rs @@ -26,7 +26,7 @@ mod brush; mod font_family; mod font_settings; mod font_stack; -mod generic; +mod generic_family; pub mod setting; mod style_property; @@ -35,5 +35,5 @@ pub use brush::Brush; pub use font_family::FontFamily; pub use font_settings::{FontFeature, FontSettings, FontVariation}; pub use font_stack::FontStack; -pub use generic::GenericFamily; +pub use generic_family::GenericFamily; pub use style_property::StyleProperty; From 7807707587a3ce053501373b050d1af4079f7717 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sun, 8 Dec 2024 10:42:52 +0700 Subject: [PATCH 11/16] Impl bytemuck for GenericFamily. We then use that to get the max value for the GenericFamilyMap in fontique. --- Cargo.lock | 6 +- Cargo.toml | 1 + fontique/Cargo.toml | 1 + fontique/src/generic.rs | 4 +- styled_text/Cargo.toml | 1 + styled_text/src/generic_family.rs | 100 ++++++++++++++++++++++++++++++ 6 files changed, 109 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8454440a..7b312c9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -480,9 +480,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" dependencies = [ "bytemuck_derive", ] @@ -963,6 +963,7 @@ dependencies = [ name = "fontique" version = "0.2.0" dependencies = [ + "bytemuck", "core-foundation", "core-text", "core_maths", @@ -2456,6 +2457,7 @@ checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" name = "styled_text" version = "0.1.0" dependencies = [ + "bytemuck", "core_maths", ] diff --git a/Cargo.toml b/Cargo.toml index b800b9c0..883bf0d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,7 @@ clippy.wildcard_dependencies = "warn" [workspace.dependencies] accesskit = "0.17" +bytemuck = { version = "1.20.0", default-features = false } fontique = { version = "0.2.0", default-features = false, path = "fontique" } hashbrown = "0.15.2" parley = { version = "0.2.0", default-features = false, path = "parley" } diff --git a/fontique/Cargo.toml b/fontique/Cargo.toml index f192ad58..96454014 100644 --- a/fontique/Cargo.toml +++ b/fontique/Cargo.toml @@ -30,6 +30,7 @@ system = [ ] [dependencies] +bytemuck = { workspace = true } read-fonts = { workspace = true } peniko = { workspace = true } styled_text = { workspace = true } diff --git a/fontique/src/generic.rs b/fontique/src/generic.rs index de6e19da..9dad952d 100644 --- a/fontique/src/generic.rs +++ b/fontique/src/generic.rs @@ -4,13 +4,13 @@ //! Generic font families. use super::FamilyId; +use bytemuck::Contiguous; // For GenericFamily::MAX_VALUE use smallvec::SmallVec; use styled_text::GenericFamily; type FamilyVec = SmallVec<[FamilyId; 2]>; -// FIXME(style): This should be done better. -const COUNT: usize = GenericFamily::FangSong as usize + 1; +const COUNT: usize = GenericFamily::MAX_VALUE as usize + 1; /// Maps generic families to family identifiers. #[derive(Clone, Default, Debug)] diff --git a/styled_text/Cargo.toml b/styled_text/Cargo.toml index d9df4915..bcca7767 100644 --- a/styled_text/Cargo.toml +++ b/styled_text/Cargo.toml @@ -21,3 +21,4 @@ libm = ["dep:core_maths"] [dependencies] core_maths = { version = "0.1.0", optional = true } +bytemuck = { workspace = true } diff --git a/styled_text/src/generic_family.rs b/styled_text/src/generic_family.rs index eb5e41d0..8ac64f8b 100644 --- a/styled_text/src/generic_family.rs +++ b/styled_text/src/generic_family.rs @@ -117,3 +117,103 @@ impl fmt::Display for GenericFamily { write!(f, "{}", name) } } + +// Safety: The enum is `repr(u8)` and has only fieldless variants. +unsafe impl bytemuck::NoUninit for GenericFamily {} + +// Safety: The enum is `repr(u8)` and `0` is a valid value. +unsafe impl bytemuck::Zeroable for GenericFamily {} + +// Safety: The enum is `repr(u8)`. +unsafe impl bytemuck::checked::CheckedBitPattern for GenericFamily { + type Bits = u8; + + fn is_valid_bit_pattern(bits: &u8) -> bool { + use bytemuck::Contiguous; + // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE. + *bits <= Self::MAX_VALUE + } +} + +// Safety: The enum is `repr(u8)`. All values are `u8` and fall within +// the min and max values. +unsafe impl bytemuck::Contiguous for GenericFamily { + type Int = u8; + const MIN_VALUE: u8 = Self::Serif as u8; + const MAX_VALUE: u8 = Self::FangSong as u8; +} + +#[cfg(test)] +mod tests { + use crate::GenericFamily; + use bytemuck::{checked::try_from_bytes, Contiguous, Zeroable}; + use core::ptr; + + #[test] + fn checked_bit_pattern() { + let valid = bytemuck::bytes_of(&2_u8); + let invalid = bytemuck::bytes_of(&200_u8); + + assert_eq!( + Ok(&GenericFamily::Monospace), + try_from_bytes::(valid) + ); + + assert!(try_from_bytes::(invalid).is_err()); + } + + #[test] + fn contiguous() { + let hd1 = GenericFamily::SansSerif; + let hd2 = GenericFamily::from_integer(hd1.into_integer()); + assert_eq!(Some(hd1), hd2); + + assert_eq!(None, GenericFamily::from_integer(255)); + } + + #[test] + fn zeroable() { + let hd = GenericFamily::zeroed(); + assert_eq!(hd, GenericFamily::Serif); + } + + /// Tests that the [`Contiguous`] impl for [`GenericFamily`] is not trivially incorrect. + const _: () = { + let mut value = 0; + while value <= GenericFamily::MAX_VALUE { + // Safety: In a const context, therefore if this makes an invalid GenericFamily, that will be detected. + // When updating the MSRV to 1.82 or later, this can use `&raw const value` instead of the addr_of! + let it: GenericFamily = unsafe { ptr::read((core::ptr::addr_of!(value)).cast()) }; + // Evaluate the enum value to ensure it actually has a valid tag + if it as u8 != value { + unreachable!(); + } + value += 1; + } + }; +} + +#[cfg(doctest)] +/// Doctests aren't collected under `cfg(test)`; we can use `cfg(doctest)` instead +mod doctests { + /// Validates that any new variants in `GenericFamily` has led to a change in the `Contiguous` impl. + /// Note that to test this robustly, we'd need 256 tests, which is impractical. + /// We make the assumption that all new variants will maintain contiguousness. + /// + /// ```compile_fail,E0080 + /// use bytemuck::Contiguous; + /// use styled_text::GenericFamily; + /// const { + /// let value = GenericFamily::MAX_VALUE + 1; + /// // Safety: In a const context, therefore if this makes an invalid GenericFamily, that will be detected. + /// // (Indeed, we rely upon that) + /// // When updating the MSRV to 1.82 or later, this can use `&raw const value` instead of the addr_of! + /// let it: GenericFamily = unsafe { core::ptr::read((core::ptr::addr_of!(value)).cast()) }; + /// // Evaluate the enum value to ensure it actually has an invalid tag + /// if it as u8 != value { + /// unreachable!(); + /// } + /// } + /// ``` + const _GENERIC_FAMILY: () = {}; +} From 667561ebcdd41dbd28407f77264f15591c09fc3d Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sun, 8 Dec 2024 10:56:34 +0700 Subject: [PATCH 12/16] Add README.md content and LICENSE-* --- styled_text/LICENSE-APACHE | 201 +++++++++++++++++++++++++++++++++++++ styled_text/LICENSE-MIT | 25 +++++ styled_text/README.md | 55 ++++++++++ 3 files changed, 281 insertions(+) create mode 100644 styled_text/LICENSE-APACHE create mode 100644 styled_text/LICENSE-MIT diff --git a/styled_text/LICENSE-APACHE b/styled_text/LICENSE-APACHE new file mode 100644 index 00000000..16fe87b0 --- /dev/null +++ b/styled_text/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/styled_text/LICENSE-MIT b/styled_text/LICENSE-MIT new file mode 100644 index 00000000..6601faa0 --- /dev/null +++ b/styled_text/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright 2024 the Parley Authors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/styled_text/README.md b/styled_text/README.md index e69de29b..c3d8853b 100644 --- a/styled_text/README.md +++ b/styled_text/README.md @@ -0,0 +1,55 @@ +
+ +# Styled Text + +**Text Styles for Styled Text** + +[![Latest published styled_text version.](https://img.shields.io/crates/v/styled_text.svg)](https://crates.io/crates/styled_text) +[![Documentation build status.](https://img.shields.io/docsrs/styled_text.svg)](https://docs.rs/styled_text) +[![Dependency staleness status.](https://deps.rs/crate/styled_text/latest/status.svg)](https://deps.rs/crate/styled_text) +[![Linebender Zulip chat.](https://img.shields.io/badge/Linebender-%23text-blue?logo=Zulip)](https://xi.zulipchat.com/#narrow/stream/205635-text) +[![Apache 2.0 or MIT license.](https://img.shields.io/badge/license-Apache--2.0_OR_MIT-blue.svg)](#license) + +
+ +Styled Text provides font enumeration and fallback. + +## Minimum supported Rust Version (MSRV) + +This version of Styled Text has been verified to compile with **Rust 1.75** and later. + +Future versions of Styled Text might increase the Rust version requirement. +It will not be treated as a breaking change and as such can even happen with small patch releases. + +
+Click here if compiling fails. + +As time has passed, some of Styled Text's dependencies could have released versions with a higher Rust requirement. +If you encounter a compilation issue due to a dependency and don't want to upgrade your Rust toolchain, then you could downgrade the dependency. + +```sh +# Use the problematic dependency's name and version +cargo update -p package_name --precise 0.1.1 +``` +
+ +## Community + +Discussion of Styled Text development happens in the [Linebender Zulip](https://xi.zulipchat.com/), specifically the [#text stream](https://xi.zulipchat.com/#narrow/stream/205635-text). +All public content can be read without logging in. + +Contributions are welcome by pull request. The [Rust code of conduct] applies. + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache 2.0 license, shall be licensed as noted in the [License](#license) section, without any additional terms or conditions. + +## License + +Licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +[Rust code of conduct]: https://www.rust-lang.org/policies/code-of-conduct + From 7db0cb826fb46d0b8aa75a4f73be20d74193e213 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sun, 8 Dec 2024 11:45:37 +0700 Subject: [PATCH 13/16] Remove outdated comments --- styled_text/src/font_settings.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/styled_text/src/font_settings.rs b/styled_text/src/font_settings.rs index 433ef47d..877bb776 100644 --- a/styled_text/src/font_settings.rs +++ b/styled_text/src/font_settings.rs @@ -6,11 +6,9 @@ use alloc::borrow::ToOwned; use core::fmt; /// Setting for a font variation. -// FIXME(style): We should copy the Setting definition from swash instead of having a dep pub type FontVariation = crate::setting::Setting; /// Setting for a font feature. -// FIXME(style): We should copy the Setting definition from swash instead of having a dep pub type FontFeature = crate::setting::Setting; /// Font settings that can be supplied as a raw source string or From f976bf400056cadcfb55974e554e6189ba8c7fa3 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sun, 8 Dec 2024 13:49:52 +0700 Subject: [PATCH 14/16] Move `TextStyle` into `styled_text` --- parley/src/style/mod.rs | 75 +-------------------------------- styled_text/src/lib.rs | 5 ++- styled_text/src/text_style.rs | 78 +++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 75 deletions(-) create mode 100644 styled_text/src/text_style.rs diff --git a/parley/src/style/mod.rs b/parley/src/style/mod.rs index 5717f76e..62a83690 100644 --- a/parley/src/style/mod.rs +++ b/parley/src/style/mod.rs @@ -5,10 +5,9 @@ mod styleset; -use alloc::borrow::Cow; pub use styled_text::{ Brush, FontFamily, FontSettings, FontStack, GenericFamily, Stretch as FontStretch, - Style as FontStyle, StyleProperty, Weight as FontWeight, + Style as FontStyle, StyleProperty, TextStyle, Weight as FontWeight, }; pub use styleset::StyleSet; @@ -24,75 +23,3 @@ pub enum WhiteSpaceCollapse { Collapse, Preserve, } - -/// Unresolved styles. -#[derive(Clone, PartialEq, Debug)] -pub struct TextStyle<'a, B: Brush> { - /// Font family stack. - pub font_stack: FontStack<'a>, - /// Font size. - pub font_size: f32, - /// Font stretch. - pub font_stretch: FontStretch, - /// Font style. - pub font_style: FontStyle, - /// Font weight. - pub font_weight: FontWeight, - /// Font variation settings. - pub font_variations: FontSettings<'a, styled_text::FontVariation>, - /// Font feature settings. - pub font_features: FontSettings<'a, styled_text::FontFeature>, - /// Locale. - pub locale: Option<&'a str>, - /// Brush for rendering text. - pub brush: B, - /// Underline decoration. - pub has_underline: bool, - /// Offset of the underline decoration. - pub underline_offset: Option, - /// Size of the underline decoration. - pub underline_size: Option, - /// Brush for rendering the underline decoration. - pub underline_brush: Option, - /// Strikethrough decoration. - pub has_strikethrough: bool, - /// Offset of the strikethrough decoration. - pub strikethrough_offset: Option, - /// Size of the strikethrough decoration. - pub strikethrough_size: Option, - /// Brush for rendering the strikethrough decoration. - pub strikethrough_brush: Option, - /// Line height multiplier. - pub line_height: f32, - /// Extra spacing between words. - pub word_spacing: f32, - /// Extra spacing between letters. - pub letter_spacing: f32, -} - -impl Default for TextStyle<'_, B> { - fn default() -> Self { - TextStyle { - font_stack: FontStack::Source(Cow::Borrowed("sans-serif")), - font_size: 16.0, - font_stretch: Default::default(), - font_style: Default::default(), - font_weight: Default::default(), - font_variations: FontSettings::List(Cow::Borrowed(&[])), - font_features: FontSettings::List(Cow::Borrowed(&[])), - locale: Default::default(), - brush: Default::default(), - has_underline: Default::default(), - underline_offset: Default::default(), - underline_size: Default::default(), - underline_brush: Default::default(), - has_strikethrough: Default::default(), - strikethrough_offset: Default::default(), - strikethrough_size: Default::default(), - strikethrough_brush: Default::default(), - line_height: 1.2, - word_spacing: Default::default(), - letter_spacing: Default::default(), - } - } -} diff --git a/styled_text/src/lib.rs b/styled_text/src/lib.rs index 365b1cb7..6cd26513 100644 --- a/styled_text/src/lib.rs +++ b/styled_text/src/lib.rs @@ -27,8 +27,10 @@ mod font_family; mod font_settings; mod font_stack; mod generic_family; -pub mod setting; mod style_property; +mod text_style; + +pub mod setting; pub use attributes::{Stretch, Style, Weight}; pub use brush::Brush; @@ -37,3 +39,4 @@ pub use font_settings::{FontFeature, FontSettings, FontVariation}; pub use font_stack::FontStack; pub use generic_family::GenericFamily; pub use style_property::StyleProperty; +pub use text_style::TextStyle; diff --git a/styled_text/src/text_style.rs b/styled_text/src/text_style.rs new file mode 100644 index 00000000..51e023b6 --- /dev/null +++ b/styled_text/src/text_style.rs @@ -0,0 +1,78 @@ +// Copyright 2021 the Parley Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use alloc::borrow::Cow; + +use crate::{Brush, FontFeature, FontSettings, FontStack, FontVariation, Stretch, Style, Weight}; + +/// Unresolved styles. +#[derive(Clone, PartialEq, Debug)] +pub struct TextStyle<'a, B: Brush> { + /// Font family stack. + pub font_stack: FontStack<'a>, + /// Font size. + pub font_size: f32, + /// Font stretch. + pub font_stretch: Stretch, + /// Font style. + pub font_style: Style, + /// Font weight. + pub font_weight: Weight, + /// Font variation settings. + pub font_variations: FontSettings<'a, FontVariation>, + /// Font feature settings. + pub font_features: FontSettings<'a, FontFeature>, + /// Locale. + pub locale: Option<&'a str>, + /// Brush for rendering text. + pub brush: B, + /// Underline decoration. + pub has_underline: bool, + /// Offset of the underline decoration. + pub underline_offset: Option, + /// Size of the underline decoration. + pub underline_size: Option, + /// Brush for rendering the underline decoration. + pub underline_brush: Option, + /// Strikethrough decoration. + pub has_strikethrough: bool, + /// Offset of the strikethrough decoration. + pub strikethrough_offset: Option, + /// Size of the strikethrough decoration. + pub strikethrough_size: Option, + /// Brush for rendering the strikethrough decoration. + pub strikethrough_brush: Option, + /// Line height multiplier. + pub line_height: f32, + /// Extra spacing between words. + pub word_spacing: f32, + /// Extra spacing between letters. + pub letter_spacing: f32, +} + +impl Default for TextStyle<'_, B> { + fn default() -> Self { + TextStyle { + font_stack: FontStack::Source(Cow::Borrowed("sans-serif")), + font_size: 16.0, + font_stretch: Default::default(), + font_style: Default::default(), + font_weight: Default::default(), + font_variations: FontSettings::List(Cow::Borrowed(&[])), + font_features: FontSettings::List(Cow::Borrowed(&[])), + locale: Default::default(), + brush: Default::default(), + has_underline: Default::default(), + underline_offset: Default::default(), + underline_size: Default::default(), + underline_brush: Default::default(), + has_strikethrough: Default::default(), + strikethrough_offset: Default::default(), + strikethrough_size: Default::default(), + strikethrough_brush: Default::default(), + line_height: 1.2, + word_spacing: Default::default(), + letter_spacing: Default::default(), + } + } +} From 0e54031cc175ce5af1944639a953ef1e0d23b44d Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sun, 8 Dec 2024 14:09:12 +0700 Subject: [PATCH 15/16] Clean up some imports --- parley/src/builder.rs | 3 ++- parley/src/context.rs | 2 +- parley/src/layout/alignment.rs | 2 +- parley/src/layout/data.rs | 2 +- parley/src/layout/editor.rs | 5 +++-- parley/src/layout/line/greedy.rs | 5 ++--- parley/src/layout/mod.rs | 2 +- parley/src/resolve/mod.rs | 7 ++----- parley/src/shape.rs | 3 ++- 9 files changed, 15 insertions(+), 16 deletions(-) diff --git a/parley/src/builder.rs b/parley/src/builder.rs index 6315508f..e88234db 100644 --- a/parley/src/builder.rs +++ b/parley/src/builder.rs @@ -4,13 +4,14 @@ //! Context for layout. use super::context::LayoutContext; -use super::style::{Brush, StyleProperty, TextStyle, WhiteSpaceCollapse}; +use super::style::WhiteSpaceCollapse; use super::FontContext; use super::layout::Layout; use alloc::string::String; use core::ops::RangeBounds; +use styled_text::{Brush, StyleProperty, TextStyle}; use crate::inline_box::InlineBox; diff --git a/parley/src/context.rs b/parley/src/context.rs index 006da787..086b8971 100644 --- a/parley/src/context.rs +++ b/parley/src/context.rs @@ -10,9 +10,9 @@ use self::tree::TreeStyleBuilder; use super::bidi; use super::builder::RangedBuilder; use super::resolve::{tree, RangedStyle, RangedStyleBuilder, ResolveContext, ResolvedStyle}; -use super::style::{Brush, TextStyle}; use super::FontContext; +use styled_text::{Brush, TextStyle}; use swash::shape::ShapeContext; use swash::text::cluster::CharInfo; diff --git a/parley/src/layout/alignment.rs b/parley/src/layout/alignment.rs index cc45cc6f..185806ec 100644 --- a/parley/src/layout/alignment.rs +++ b/parley/src/layout/alignment.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use super::{Alignment, BreakReason, LayoutData}; -use crate::style::Brush; +use styled_text::Brush; pub(crate) fn align( layout: &mut LayoutData, diff --git a/parley/src/layout/data.rs b/parley/src/layout/data.rs index 1814cd1d..1c767cd1 100644 --- a/parley/src/layout/data.rs +++ b/parley/src/layout/data.rs @@ -3,10 +3,10 @@ use crate::inline_box::InlineBox; use crate::layout::{Alignment, Glyph, LineMetrics, RunMetrics, Style}; -use crate::style::Brush; use crate::util::nearly_zero; use crate::Font; use core::ops::Range; +use styled_text::Brush; use swash::shape::Shaper; use swash::text::cluster::{Boundary, ClusterInfo}; use swash::Synthesis; diff --git a/parley/src/layout/editor.rs b/parley/src/layout/editor.rs index 45fbbe86..ca80ea7c 100644 --- a/parley/src/layout/editor.rs +++ b/parley/src/layout/editor.rs @@ -8,8 +8,8 @@ use crate::{ cursor::{Cursor, Selection}, Affinity, Alignment, Layout, }, - style::Brush, - FontContext, LayoutContext, Rect, StyleProperty, StyleSet, + style::StyleSet, + FontContext, LayoutContext, Rect, }; use alloc::{borrow::ToOwned, string::String, vec::Vec}; use core::{ @@ -18,6 +18,7 @@ use core::{ fmt::{Debug, Display}, ops::Range, }; +use styled_text::{Brush, StyleProperty}; #[cfg(feature = "accesskit")] use crate::layout::LayoutAccessibility; diff --git a/parley/src/layout/line/greedy.rs b/parley/src/layout/line/greedy.rs index 77541e5f..6e97f915 100644 --- a/parley/src/layout/line/greedy.rs +++ b/parley/src/layout/line/greedy.rs @@ -4,6 +4,8 @@ //! Greedy line breaking. use alloc::vec::Vec; +use core::ops::Range; +use styled_text::Brush; use swash::text::cluster::Whitespace; #[cfg(feature = "libm")] @@ -15,9 +17,6 @@ use crate::layout::{ Alignment, Boundary, BreakReason, Layout, LayoutData, LayoutItem, LayoutItemKind, LineData, LineItemData, LineMetrics, Run, }; -use crate::style::Brush; - -use core::ops::Range; #[derive(Default)] struct LineLayout { diff --git a/parley/src/layout/mod.rs b/parley/src/layout/mod.rs index a9b5e679..802b74aa 100644 --- a/parley/src/layout/mod.rs +++ b/parley/src/layout/mod.rs @@ -15,7 +15,6 @@ pub mod editor; use self::alignment::align; -use super::style::Brush; use crate::{Font, InlineBox}; #[cfg(feature = "accesskit")] use accesskit::{Node, NodeId, Role, TextDirection, TreeUpdate}; @@ -28,6 +27,7 @@ use data::{ }; #[cfg(feature = "accesskit")] use hashbrown::{HashMap, HashSet}; +use styled_text::Brush; use swash::text::cluster::{Boundary, ClusterInfo}; use swash::{GlyphId, NormalizedCoord, Synthesis}; diff --git a/parley/src/resolve/mod.rs b/parley/src/resolve/mod.rs index f0cf6834..02fa7014 100644 --- a/parley/src/resolve/mod.rs +++ b/parley/src/resolve/mod.rs @@ -10,17 +10,14 @@ pub(crate) use range::RangedStyleBuilder; use alloc::{vec, vec::Vec}; -use super::style::{ - Brush, FontFamily, FontFeature, FontSettings, FontStack, FontStretch, FontStyle, FontVariation, - FontWeight, StyleProperty, -}; use crate::font::FontContext; use crate::layout; -use crate::style::TextStyle; +use crate::style::{FontFeature, FontStretch, FontStyle, FontVariation, FontWeight}; use crate::util::nearly_eq; use core::borrow::Borrow; use core::ops::Range; use fontique::FamilyId; +use styled_text::{Brush, FontFamily, FontSettings, FontStack, StyleProperty, TextStyle}; use swash::text::Language; /// Style with an associated range. diff --git a/parley/src/shape.rs b/parley/src/shape.rs index d3ce8053..b6a3836d 100644 --- a/parley/src/shape.rs +++ b/parley/src/shape.rs @@ -3,11 +3,12 @@ use super::layout::Layout; use super::resolve::{RangedStyle, ResolveContext, Resolved}; -use super::style::{Brush, FontFeature, FontVariation}; +use crate::style::{FontFeature, FontVariation}; use crate::util::nearly_eq; use crate::Font; use fontique::QueryFamily; use fontique::{self, Query, QueryFont}; +use styled_text::Brush; use swash::shape::{partition, Direction, ShapeContext}; use swash::text::cluster::{CharCluster, CharInfo, Token}; use swash::text::{Language, Script}; From 48294c603778fe011ea8a0fcf91e0388fa7fae1c Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Sun, 8 Dec 2024 14:45:23 +0700 Subject: [PATCH 16/16] Move FontConfig conversion to `styled_text` This also adds additional cases for the stretch conversion and a note in the doc comments about where these values have come from. --- fontique/src/backend/fontconfig/cache.rs | 59 +----------------- styled_text/src/attributes.rs | 77 ++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 56 deletions(-) diff --git a/fontique/src/backend/fontconfig/cache.rs b/fontique/src/backend/fontconfig/cache.rs index cf60e474..b0d1e64b 100644 --- a/fontique/src/backend/fontconfig/cache.rs +++ b/fontique/src/backend/fontconfig/cache.rs @@ -6,59 +6,6 @@ use std::io::Read; use std::path::PathBuf; use styled_text::{Stretch, Style, Weight}; -// FIXME(style): There's an argument for putting this into styled_text -fn stretch_from_fc(width: i32) -> Stretch { - match width { - 63 => Stretch::EXTRA_CONDENSED, - 87 => Stretch::SEMI_CONDENSED, - 113 => Stretch::SEMI_EXPANDED, - _ => Stretch::from_ratio(width as f32 / 100.0), - } -} - -// FIXME(style): There's an argument for putting this into styled_text -fn style_from_fc(slant: i32) -> Style { - match slant { - 100 => Style::Italic, - 110 => Style::Oblique(None), - _ => Style::Normal, - } -} - -// FIXME(style): There's an argument for putting this into styled_text -fn weight_from_fc(weight: i32) -> Weight { - const MAP: &[(i32, i32)] = &[ - (0, 0), - (100, 0), - (200, 40), - (300, 50), - (350, 55), - (380, 75), - (400, 80), - (500, 100), - (600, 180), - (700, 200), - (800, 205), - (900, 210), - (950, 215), - ]; - for (i, (ot, fc)) in MAP.iter().skip(1).enumerate() { - if weight == *fc { - return Weight::new(*ot as f32); - } - if weight < *fc { - let weight = weight as f32; - let fc_a = MAP[i - 1].1 as f32; - let fc_b = *fc as f32; - let ot_a = MAP[i - 1].1 as f32; - let ot_b = *ot as f32; - let t = (fc_a - fc_b) / (weight - fc_a); - return Weight::new(ot_a + (ot_b - ot_a) * t); - } - } - Weight::EXTRA_BLACK -} - #[derive(Default)] pub struct CachedFont { pub family: Vec, @@ -153,21 +100,21 @@ fn parse_font( Object::Slant => { for val in elt.values().ok()? { if let Value::Int(i) = val.ok()? { - font.style = style_from_fc(i as _); + font.style = Style::from_fontconfig(i as _); } } } Object::Weight => { for val in elt.values().ok()? { if let Value::Int(i) = val.ok()? { - font.weight = weight_from_fc(i as _); + font.weight = Weight::from_fontconfig(i as _); } } } Object::Width => { for val in elt.values().ok()? { if let Value::Int(i) = val.ok()? { - font.stretch = stretch_from_fc(i as _); + font.stretch = Stretch::from_fontconfig(i as _); } } } diff --git a/styled_text/src/attributes.rs b/styled_text/src/attributes.rs index af96013b..6ff65455 100644 --- a/styled_text/src/attributes.rs +++ b/styled_text/src/attributes.rs @@ -156,6 +156,28 @@ impl Stretch { } } +impl Stretch { + /// Creates a new stretch attribute with the given value from Fontconfig. + /// + /// The values are determined based on the [fonts.conf documentation]. + /// + /// [fonts.conf documentation]: https://www.freedesktop.org/software/fontconfig/fontconfig-user.html + pub fn from_fontconfig(width: i32) -> Self { + match width { + 50 => Self::ULTRA_CONDENSED, + 63 => Self::EXTRA_CONDENSED, + 75 => Self::CONDENSED, + 87 => Self::SEMI_CONDENSED, + 100 => Self::NORMAL, + 113 => Self::SEMI_EXPANDED, + 125 => Self::EXPANDED, + 150 => Self::EXTRA_EXPANDED, + 200 => Self::ULTRA_EXPANDED, + _ => Self::from_ratio(width as f32 / 100.0), + } + } +} + impl fmt::Display for Stretch { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let value = self.0 * 1000.0; @@ -268,6 +290,46 @@ impl Weight { } } +impl Weight { + /// Creates a new weight attribute with the given value from Fontconfig. + /// + /// The values are determined based on the [fonts.conf documentation]. + /// + /// [fonts.conf documentation]: https://www.freedesktop.org/software/fontconfig/fontconfig-user.html + pub fn from_fontconfig(weight: i32) -> Self { + const MAP: &[(i32, i32)] = &[ + (0, 0), + (100, 0), + (200, 40), + (300, 50), + (350, 55), + (380, 75), + (400, 80), + (500, 100), + (600, 180), + (700, 200), + (800, 205), + (900, 210), + (950, 215), + ]; + for (i, (ot, fc)) in MAP.iter().skip(1).enumerate() { + if weight == *fc { + return Self::new(*ot as f32); + } + if weight < *fc { + let weight = weight as f32; + let fc_a = MAP[i - 1].1 as f32; + let fc_b = *fc as f32; + let ot_a = MAP[i - 1].1 as f32; + let ot_b = *ot as f32; + let t = (fc_a - fc_b) / (weight - fc_a); + return Self::new(ot_a + (ot_b - ot_a) * t); + } + } + Self::EXTRA_BLACK + } +} + impl Default for Weight { fn default() -> Self { Self::NORMAL @@ -362,6 +424,21 @@ impl Style { } } +impl Style { + /// Creates a new style attribute with the given value from Fontconfig. + /// + /// The values are determined based on the [fonts.conf documentation]. + /// + /// [fonts.conf documentation]: https://www.freedesktop.org/software/fontconfig/fontconfig-user.html + pub fn from_fontconfig(slant: i32) -> Self { + match slant { + 100 => Self::Italic, + 110 => Self::Oblique(None), + _ => Self::Normal, + } + } +} + impl fmt::Display for Style { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let value = match self {