cairo/font/
user_fonts.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::sync::OnceLock;
4
5use super::{FontExtents, FontFace, ScaledFont, TextCluster, TextClusterFlags, TextExtents};
6use crate::{ffi, utils::status_to_result, Context, Error, Glyph};
7
8type BoxInitFunc =
9    Box<dyn Fn(&ScaledFont, &Context, &mut FontExtents) -> Result<(), Error> + Send + Sync>;
10type BoxRenderGlyphFunc = Box<
11    dyn Fn(&ScaledFont, libc::c_ulong, &Context, &mut TextExtents) -> Result<(), Error>
12        + Send
13        + Sync,
14>;
15type BoxUnicodeToGlyphFunc =
16    Box<dyn Fn(&ScaledFont, libc::c_ulong) -> Result<libc::c_ulong, Error> + Send + Sync>;
17type BoxTextToGlyphsFunc = Box<
18    dyn Fn(&ScaledFont, &str) -> Result<(Vec<Glyph>, Vec<TextCluster>, TextClusterFlags), Error>
19        + Send
20        + Sync,
21>;
22
23pub struct UserFontFace(FontFace);
24
25impl UserFontFace {
26    #[doc(alias = "cairo_user_font_face_create")]
27    pub fn create() -> Result<Self, Error> {
28        let font_face = unsafe { FontFace::from_raw_full(ffi::cairo_user_font_face_create()) };
29        let status = unsafe { ffi::cairo_font_face_status(font_face.to_raw_none()) };
30        status_to_result(status)?;
31        Ok(Self(font_face))
32    }
33
34    #[doc(alias = "cairo_user_font_face_set_init_func")]
35    pub fn set_init_func<F>(&self, func: F)
36    where
37        F: Fn(&ScaledFont, &Context, &mut FontExtents) -> Result<(), Error> + Send + Sync + 'static,
38    {
39        static INIT_FUNC: OnceLock<BoxInitFunc> = OnceLock::new();
40        if INIT_FUNC.set(Box::new(func)).is_err() {
41            panic!("Init func can only be set once")
42        }
43        unsafe extern "C" fn init_trampoline(
44            scaled_font: *mut ffi::cairo_scaled_font_t,
45            cr: *mut ffi::cairo_t,
46            extents: *mut ffi::cairo_font_extents_t,
47        ) -> ffi::cairo_status_t {
48            let font_extents = &mut *(extents as *mut FontExtents);
49            let init_func = INIT_FUNC.get().unwrap();
50            if let Err(err) = init_func(
51                &ScaledFont::from_raw_none(scaled_font),
52                &Context::from_raw_none(cr),
53                font_extents,
54            ) {
55                err.into()
56            } else {
57                ffi::STATUS_SUCCESS
58            }
59        }
60        unsafe {
61            ffi::cairo_user_font_face_set_init_func(self.to_raw_none(), Some(init_trampoline));
62        }
63    }
64
65    #[doc(alias = "cairo_user_font_face_set_render_glyph_func")]
66    pub fn set_render_glyph_func<F>(&self, func: F)
67    where
68        F: Fn(&ScaledFont, libc::c_ulong, &Context, &mut TextExtents) -> Result<(), Error>
69            + Send
70            + Sync
71            + 'static,
72    {
73        static RENDER_GLYPH_FUNC: OnceLock<BoxRenderGlyphFunc> = OnceLock::new();
74        if RENDER_GLYPH_FUNC.set(Box::new(func)).is_err() {
75            panic!("RenderGlyph func can only be set once")
76        }
77        unsafe extern "C" fn render_glyph_trampoline(
78            scaled_font: *mut ffi::cairo_scaled_font_t,
79            glyph: libc::c_ulong,
80            cr: *mut ffi::cairo_t,
81            extents: *mut ffi::cairo_text_extents_t,
82        ) -> ffi::cairo_status_t {
83            let text_extents = &mut *(extents as *mut TextExtents);
84            let render_glyph_func = RENDER_GLYPH_FUNC.get().unwrap();
85            if let Err(err) = render_glyph_func(
86                &ScaledFont::from_raw_none(scaled_font),
87                glyph,
88                &Context::from_raw_none(cr),
89                text_extents,
90            ) {
91                err.into()
92            } else {
93                ffi::STATUS_SUCCESS
94            }
95        }
96        unsafe {
97            ffi::cairo_user_font_face_set_render_glyph_func(
98                self.to_raw_none(),
99                Some(render_glyph_trampoline),
100            );
101        }
102    }
103
104    #[doc(alias = "cairo_user_font_face_set_render_color_glyph_func")]
105    pub fn set_render_color_glyph_func<F>(&self, func: F)
106    where
107        F: Fn(&ScaledFont, libc::c_ulong, &Context, &mut TextExtents) -> Result<(), Error>
108            + Send
109            + Sync
110            + 'static,
111    {
112        static RENDER_COLOR_GLYPH_FUNC: OnceLock<BoxRenderGlyphFunc> = OnceLock::new();
113        if RENDER_COLOR_GLYPH_FUNC.set(Box::new(func)).is_err() {
114            panic!("RenderColorGlyph func can only be set once")
115        }
116        unsafe extern "C" fn render_glyph_trampoline(
117            scaled_font: *mut ffi::cairo_scaled_font_t,
118            glyph: libc::c_ulong,
119            cr: *mut ffi::cairo_t,
120            extents: *mut ffi::cairo_text_extents_t,
121        ) -> ffi::cairo_status_t {
122            let text_extents = &mut *(extents as *mut TextExtents);
123            let render_glyph_func = RENDER_COLOR_GLYPH_FUNC.get().unwrap();
124            if let Err(err) = render_glyph_func(
125                &ScaledFont::from_raw_none(scaled_font),
126                glyph,
127                &Context::from_raw_none(cr),
128                text_extents,
129            ) {
130                err.into()
131            } else {
132                ffi::STATUS_SUCCESS
133            }
134        }
135        unsafe {
136            ffi::cairo_user_font_face_set_render_glyph_func(
137                self.to_raw_none(),
138                Some(render_glyph_trampoline),
139            );
140        }
141    }
142
143    #[doc(alias = "cairo_user_font_face_set_unicode_to_glyph_func")]
144    pub fn set_unicode_to_glyph_func<F>(&self, func: F)
145    where
146        F: Fn(&ScaledFont, libc::c_ulong) -> Result<libc::c_ulong, Error> + Send + Sync + 'static,
147    {
148        static UNICODE_TO_GLYPH_FUNC: OnceLock<BoxUnicodeToGlyphFunc> = OnceLock::new();
149        if UNICODE_TO_GLYPH_FUNC.set(Box::new(func)).is_err() {
150            panic!("UnicodeToGlyph func can only be set once")
151        }
152        unsafe extern "C" fn unicode_to_glyph_trampoline(
153            scaled_font: *mut ffi::cairo_scaled_font_t,
154            unicode: libc::c_ulong,
155            glyph_index: *mut libc::c_ulong,
156        ) -> ffi::cairo_status_t {
157            let unicode_to_glyph_func = UNICODE_TO_GLYPH_FUNC.get().unwrap();
158            match unicode_to_glyph_func(&ScaledFont::from_raw_none(scaled_font), unicode) {
159                Err(err) => err.into(),
160                Ok(glyph) => {
161                    *glyph_index = glyph;
162                    ffi::STATUS_SUCCESS
163                }
164            }
165        }
166        unsafe {
167            ffi::cairo_user_font_face_set_unicode_to_glyph_func(
168                self.to_raw_none(),
169                Some(unicode_to_glyph_trampoline),
170            );
171        }
172    }
173
174    #[doc(alias = "cairo_user_font_face_set_text_to_glyphs_func")]
175    pub fn set_text_to_glyphs_func<F>(&self, func: F)
176    where
177        F: Fn(&ScaledFont, &str) -> Result<(Vec<Glyph>, Vec<TextCluster>, TextClusterFlags), Error>
178            + Send
179            + Sync
180            + 'static,
181    {
182        static TEXT_TO_GLYPHS_FUNC: OnceLock<BoxTextToGlyphsFunc> = OnceLock::new();
183        if TEXT_TO_GLYPHS_FUNC.set(Box::new(func)).is_err() {
184            panic!("TextToGlyphs func can only be set once")
185        }
186        unsafe extern "C" fn text_to_glyphs_trampoline(
187            scaled_font: *mut ffi::cairo_scaled_font_t,
188            utf8: *const libc::c_char,
189            utf8_len: libc::c_int,
190            glyphs: *mut *mut ffi::cairo_glyph_t,
191            num_glyphs: *mut libc::c_int,
192            clusters: *mut *mut ffi::cairo_text_cluster_t,
193            num_clusters: *mut libc::c_int,
194            cluster_flags: *mut ffi::cairo_text_cluster_flags_t,
195        ) -> ffi::cairo_status_t {
196            let text_to_glyphs_func = TEXT_TO_GLYPHS_FUNC.get().unwrap();
197            let text = if utf8_len > 0 {
198                let bytes = std::slice::from_raw_parts(utf8 as *const u8, utf8_len as usize);
199                std::str::from_utf8_unchecked(bytes)
200            } else {
201                std::ffi::CStr::from_ptr(utf8).to_str().unwrap()
202            };
203            match text_to_glyphs_func(&ScaledFont::from_raw_none(scaled_font), text) {
204                Err(err) => err.into(),
205                Ok((glyphs_, clusters_, flags)) => {
206                    *num_glyphs = glyphs_.len() as _;
207                    let c_glyphs = ffi::cairo_glyph_allocate(*num_glyphs);
208                    std::ptr::copy_nonoverlapping(
209                        glyphs_.as_ptr(),
210                        c_glyphs as *mut _,
211                        glyphs_.len(),
212                    );
213                    *glyphs = c_glyphs;
214
215                    *num_clusters = clusters_.len() as _;
216                    let c_clusters = ffi::cairo_text_cluster_allocate(*num_clusters);
217                    std::ptr::copy_nonoverlapping(
218                        clusters_.as_ptr(),
219                        c_clusters as *mut _,
220                        clusters_.len(),
221                    );
222                    *clusters = c_clusters;
223
224                    *cluster_flags = flags.into();
225
226                    ffi::STATUS_SUCCESS
227                }
228            }
229        }
230        unsafe {
231            ffi::cairo_user_font_face_set_text_to_glyphs_func(
232                self.to_raw_none(),
233                Some(text_to_glyphs_trampoline),
234            );
235        }
236    }
237}
238
239impl std::ops::Deref for UserFontFace {
240    type Target = FontFace;
241
242    #[inline]
243    fn deref(&self) -> &Self::Target {
244        &self.0
245    }
246}