Skip to main content

skia_safe/core/
font_arguments.rs

1use std::{fmt, marker::PhantomData, mem};
2
3use skia_bindings::{
4    self as sb, SkFontArguments, SkFontArguments_Palette, SkFontArguments_VariationPosition,
5};
6
7use crate::prelude::*;
8
9/// Represents a set of actual arguments for a font.
10#[repr(C)]
11pub struct FontArguments<'vp, 'p> {
12    args: SkFontArguments,
13    pd_vp: PhantomData<&'vp [variation_position::Coordinate]>,
14    pd_p: PhantomData<&'p [palette::Override]>,
15}
16
17native_transmutable!(SkFontArguments, FontArguments<'_, '_>);
18
19/// Represents a position in the variation design space.
20///
21/// Any axis not specified uses the default value.
22/// Any specified axis not actually present in the font is ignored.
23#[derive(Clone, Debug)]
24pub struct VariationPosition<'a> {
25    pub coordinates: &'a [variation_position::Coordinate],
26}
27
28pub mod variation_position {
29    use crate::FourByteTag;
30    use skia_bindings::SkFontArguments_VariationPosition_Coordinate;
31
32    /// A single axis/value pair in a [`crate::font_arguments::VariationPosition`].
33    #[derive(Copy, Clone, PartialEq, Default, Debug)]
34    #[repr(C)]
35    pub struct Coordinate {
36        pub axis: FourByteTag,
37        pub value: f32,
38    }
39
40    native_transmutable!(SkFontArguments_VariationPosition_Coordinate, Coordinate);
41
42    #[allow(non_upper_case_globals)]
43    impl Coordinate {
44        pub const wght: FourByteTag = FourByteTag::from_chars('w', 'g', 'h', 't');
45        pub const wdth: FourByteTag = FourByteTag::from_chars('w', 'd', 't', 'h');
46        pub const slnt: FourByteTag = FourByteTag::from_chars('s', 'l', 'n', 't');
47        pub const ital: FourByteTag = FourByteTag::from_chars('i', 't', 'a', 'l');
48        pub const opsz: FourByteTag = FourByteTag::from_chars('o', 'p', 's', 'z');
49    }
50}
51
52/// Specifies a palette to use and overrides for palette entries.
53///
54/// `overrides` is a list of pairs of palette entry index and color.
55/// Overridden palette entries use the associated color.
56///
57/// Override pairs with palette entry indices out of range are not applied.
58/// Later override entries override earlier ones.
59#[derive(Clone, Debug)]
60pub struct Palette<'a> {
61    pub index: i32,
62    pub overrides: &'a [palette::Override],
63}
64
65pub mod palette {
66    use crate::Color;
67    use skia_bindings::SkFontArguments_Palette_Override;
68
69    /// A palette entry override.
70    #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
71    #[repr(C)]
72    pub struct Override {
73        pub index: u16,
74        pub color: Color,
75    }
76
77    native_transmutable!(SkFontArguments_Palette_Override, Override);
78}
79
80impl Drop for FontArguments<'_, '_> {
81    fn drop(&mut self) {
82        unsafe { sb::C_SkFontArguments_destruct(self.native_mut()) }
83    }
84}
85
86impl Default for FontArguments<'_, '_> {
87    fn default() -> Self {
88        FontArguments::new()
89    }
90}
91
92impl fmt::Debug for FontArguments<'_, '_> {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        f.debug_struct("FontArguments")
95            .field("collection_index", &self.collection_index())
96            .field(
97                "variation_design_position",
98                &self.variation_design_position(),
99            )
100            .field("palette", &self.palette())
101            .field("synthetic_bold", &self.synthetic_bold())
102            .field("synthetic_oblique", &self.synthetic_oblique())
103            .finish()
104    }
105}
106
107impl FontArguments<'_, '_> {
108    /// Creates default font arguments.
109    pub fn new() -> Self {
110        Self::construct(|fa| unsafe {
111            sb::C_SkFontArguments_construct(fa);
112        })
113    }
114
115    /// Specifies the index of the desired font.
116    ///
117    /// Font formats like ttc, dfont, cff, cid, pfr, t42, t1, and fon may actually be indexed
118    /// collections of fonts.
119    ///
120    /// - `collection_index`: index of the font in an indexed collection.
121    pub fn set_collection_index(&mut self, collection_index: usize) -> &mut Self {
122        self.native_mut().fCollectionIndex = collection_index.try_into().unwrap();
123        self
124    }
125
126    // This function consumes self for it to be able to change its lifetime,
127    // because it borrows the coordinates referenced by [`VariationPosition`].
128    //
129    // If we would return `Self`, position's Coordinates would not be borrowed.
130    /// Specifies a position in the variation design space.
131    ///
132    /// Any axis not specified uses the default value.
133    /// Any specified axis not actually present in the font is ignored.
134    ///
135    /// This borrows `position` data; the value must remain valid for the lifetime of
136    /// [`FontArguments`].
137    ///
138    /// - `position`: variation coordinates to use.
139    pub fn set_variation_design_position(mut self, position: VariationPosition) -> FontArguments {
140        let position = SkFontArguments_VariationPosition {
141            coordinates: position.coordinates.native().as_ptr(),
142            coordinateCount: position.coordinates.len().try_into().unwrap(),
143        };
144        unsafe {
145            sb::C_SkFontArguments_setVariationDesignPosition(self.native_mut(), position);
146            // note: we are _not_ returning Self here, but VariationPosition with a
147            // changed lifetime.
148            mem::transmute(self)
149        }
150    }
151
152    /// Returns the index of the selected font in an indexed collection.
153    pub fn collection_index(&self) -> usize {
154        self.native().fCollectionIndex.try_into().unwrap()
155    }
156
157    /// Returns the variation design position.
158    pub fn variation_design_position(&self) -> VariationPosition {
159        unsafe {
160            let position = sb::C_SkFontArguments_getVariationDesignPosition(self.native());
161            VariationPosition {
162                coordinates: safer::from_raw_parts(
163                    position.coordinates as *const _,
164                    position.coordinateCount.try_into().unwrap(),
165                ),
166            }
167        }
168    }
169
170    // This function consumes `self` for it to be able to change its lifetime, because it borrows
171    // the coordinates referenced by `[Palette]`.
172    /// Specifies the color palette and optional palette entry overrides.
173    ///
174    /// This borrows `palette` data; the value must remain valid for the lifetime of
175    /// [`FontArguments`].
176    ///
177    /// - `palette`: palette index and override entries.
178    pub fn set_palette(mut self, palette: Palette) -> FontArguments {
179        let palette = SkFontArguments_Palette {
180            index: palette.index,
181            overrides: palette.overrides.native().as_ptr(),
182            overrideCount: palette.overrides.len().try_into().unwrap(),
183        };
184        unsafe {
185            sb::C_SkFontArguments_setPalette(self.native_mut(), palette);
186            mem::transmute(self)
187        }
188    }
189
190    /// Returns the palette selection and override entries.
191    pub fn palette(&self) -> Palette {
192        unsafe {
193            let palette = sb::C_SkFontArguments_getPalette(self.native());
194            Palette {
195                index: palette.index,
196                overrides: safer::from_raw_parts(
197                    palette.overrides as *const _,
198                    palette.overrideCount.try_into().unwrap(),
199                ),
200            }
201        }
202    }
203
204    /// Sets whether synthetic bold styling is requested.
205    ///
206    /// - `synthetic_bold`: `Some(true)` to force synthetic bold,
207    ///   `Some(false)` to force non-bold, `None` to leave unspecified.
208    pub fn set_synthetic_bold(&mut self, synthetic_bold: impl Into<Option<bool>>) -> &mut Self {
209        unsafe {
210            sb::C_SkFontArguments_setSyntheticBold(
211                self.native_mut(),
212                option_bool_to_ffi(synthetic_bold.into()),
213            );
214        }
215        self
216    }
217
218    /// Returns the synthetic bold preference, if specified.
219    pub fn synthetic_bold(&self) -> Option<bool> {
220        ffi_to_option_bool(unsafe { sb::C_SkFontArguments_getSyntheticBold(self.native()) })
221    }
222
223    /// Sets whether synthetic oblique styling is requested.
224    ///
225    /// - `synthetic_oblique`: `Some(true)` to force synthetic oblique,
226    ///   `Some(false)` to force non-oblique, `None` to leave unspecified.
227    pub fn set_synthetic_oblique(
228        &mut self,
229        synthetic_oblique: impl Into<Option<bool>>,
230    ) -> &mut Self {
231        unsafe {
232            sb::C_SkFontArguments_setSyntheticOblique(
233                self.native_mut(),
234                option_bool_to_ffi(synthetic_oblique.into()),
235            );
236        }
237        self
238    }
239
240    /// Returns the synthetic oblique preference, if specified.
241    pub fn synthetic_oblique(&self) -> Option<bool> {
242        ffi_to_option_bool(unsafe { sb::C_SkFontArguments_getSyntheticOblique(self.native()) })
243    }
244}
245
246fn option_bool_to_ffi(value: Option<bool>) -> i32 {
247    match value {
248        Some(true) => 1,
249        Some(false) => 0,
250        None => -1,
251    }
252}
253
254fn ffi_to_option_bool(value: i32) -> Option<bool> {
255    match value {
256        1 => Some(true),
257        0 => Some(false),
258        _ => None,
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265
266    #[test]
267    fn test_font_arguments_with_no_coordinates() {
268        let fa = FontArguments::new();
269        let coordinates = fa.variation_design_position();
270        assert_eq!(coordinates.coordinates, []);
271    }
272
273    #[test]
274    #[allow(clippy::float_cmp)]
275    fn access_coordinates() {
276        let coordinates = Box::new([variation_position::Coordinate {
277            axis: 0.into(),
278            value: 1.0,
279        }]);
280        let args = FontArguments::new();
281        let pos = VariationPosition {
282            coordinates: coordinates.as_ref(),
283        };
284        let args = args.set_variation_design_position(pos);
285        assert_eq!(args.variation_design_position().coordinates[0].value, 1.0);
286        drop(args);
287    }
288
289    #[test]
290    fn synthetic_style_flags_roundtrip() {
291        let mut args = FontArguments::new();
292
293        assert_eq!(args.synthetic_bold(), None);
294        assert_eq!(args.synthetic_oblique(), None);
295
296        args.set_synthetic_bold(Some(true));
297        assert_eq!(args.synthetic_bold(), Some(true));
298
299        args.set_synthetic_bold(Some(false));
300        assert_eq!(args.synthetic_bold(), Some(false));
301
302        args.set_synthetic_bold(None);
303        assert_eq!(args.synthetic_bold(), None);
304
305        args.set_synthetic_oblique(Some(true));
306        assert_eq!(args.synthetic_oblique(), Some(true));
307
308        args.set_synthetic_oblique(Some(false));
309        assert_eq!(args.synthetic_oblique(), Some(false));
310
311        args.set_synthetic_oblique(None);
312        assert_eq!(args.synthetic_oblique(), None);
313    }
314}