1use 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}