1#![allow(clippy::upper_case_acronyms)]
7
8use std::ffi::c_void;
9use std::rc::Rc;
10
11use core_foundation::{
12 array::{CFArray, CFArrayRef, CFIndex},
13 attributed_string::CFMutableAttributedString,
14 base::{CFTypeID, TCFType},
15 declare_TCFType,
16 dictionary::{CFDictionary, CFDictionaryRef},
17 impl_TCFType,
18 number::CFNumber,
19 string::{CFString, CFStringRef},
20};
21use core_foundation_sys::base::CFRange;
22use core_graphics::{
23 base::CGFloat,
24 color::CGColor,
25 context::CGContextRef,
26 data_provider::CGDataProvider,
27 font::CGFont,
28 geometry::{CGAffineTransform, CGPoint, CGRect, CGSize},
29 path::CGPathRef,
30};
31use core_text::{
32 font::{
33 self, CTFont, CTFontRef, CTFontUIFontType, kCTFontSystemFontType,
34 kCTFontUserFixedPitchFontType,
35 },
36 font_collection::{self, CTFontCollection, CTFontCollectionRef},
37 font_descriptor::{self, CTFontDescriptor, CTFontDescriptorRef},
38 frame::CTFrame,
39 framesetter::CTFramesetter,
40 line::{CTLine, CTLineRef, TypographicBounds},
41 string_attributes,
42};
43use foreign_types::{ForeignType, ForeignTypeRef};
44
45use piet::kurbo::{Affine, Rect};
46use piet::{Color, FontFamily, FontFamilyInner, TextAlignment, util};
47
48#[derive(Clone)]
49pub(crate) struct AttributedString {
50 pub(crate) inner: CFMutableAttributedString,
51 rtl: bool,
53}
54
55#[derive(Debug, Clone)]
56pub(crate) struct Framesetter(CTFramesetter);
57#[derive(Debug, Clone)]
58pub(crate) struct Frame {
59 frame: CTFrame,
60 lines: Rc<[Line]>,
61}
62#[derive(Debug, Clone)]
63pub(crate) struct Line(CTLine);
64
65#[derive(Debug, Clone)]
66pub(crate) struct FontCollection(CTFontCollection);
67
68pub enum __CTParagraphStyle {}
69type CTParagraphStyleRef = *const __CTParagraphStyle;
70
71declare_TCFType!(CTParagraphStyle, CTParagraphStyleRef);
72impl_TCFType!(
73 CTParagraphStyle,
74 CTParagraphStyleRef,
75 CTParagraphStyleGetTypeID
76);
77
78#[repr(u32)]
79enum CTParagraphStyleSpecifier {
80 Alignment = 0,
81 }
89
90#[repr(u8)]
91enum CTTextAlignment {
92 Left = 0,
93 Right = 1,
94 Center = 2,
95 Justified = 3,
96 Natural = 4,
97}
98
99#[repr(C)]
100struct CTParagraphStyleSetting {
101 spec: CTParagraphStyleSpecifier,
102 value_size: usize,
103 value: *const c_void,
104}
105
106impl CTParagraphStyleSetting {
107 fn alignment(alignment: TextAlignment, is_rtl: bool) -> Self {
108 static LEFT: CTTextAlignment = CTTextAlignment::Left;
109 static RIGHT: CTTextAlignment = CTTextAlignment::Right;
110 static CENTER: CTTextAlignment = CTTextAlignment::Center;
111 static JUSTIFIED: CTTextAlignment = CTTextAlignment::Justified;
112 static NATURAL: CTTextAlignment = CTTextAlignment::Natural;
113
114 let alignment: *const CTTextAlignment = match alignment {
115 TextAlignment::Start => &NATURAL,
116 TextAlignment::End if is_rtl => &LEFT,
117 TextAlignment::End => &RIGHT,
118 TextAlignment::Center => &CENTER,
119 TextAlignment::Justified => &JUSTIFIED,
120 };
121
122 CTParagraphStyleSetting {
123 spec: CTParagraphStyleSpecifier::Alignment,
124 value: alignment as *const c_void,
125 value_size: std::mem::size_of::<CTTextAlignment>(),
126 }
127 }
128}
129
130impl AttributedString {
131 pub(crate) fn new(text: &str) -> Self {
132 let mut inner = CFMutableAttributedString::new();
133 let range = CFRange::init(0, 0);
134 let cf_string = CFString::new(text);
135 inner.replace_str(&cf_string, range);
136 let rtl = util::first_strong_rtl(text);
137 AttributedString { inner, rtl }
138 }
139
140 pub(crate) fn set_alignment(&mut self, alignment: TextAlignment) {
141 let alignment = CTParagraphStyleSetting::alignment(alignment, self.rtl);
142 let settings = [alignment];
143 unsafe {
144 let style = CTParagraphStyleCreate(settings.as_ptr(), 1);
145 let style = CTParagraphStyle::wrap_under_create_rule(style);
146 self.inner.set_attribute(
147 self.range(),
148 string_attributes::kCTParagraphStyleAttributeName,
149 &style,
150 );
151 }
152 }
153
154 pub(crate) fn set_font(&mut self, range: CFRange, font: &CTFont) {
155 unsafe {
156 self.inner
157 .set_attribute(range, string_attributes::kCTFontAttributeName, font);
158 }
159 }
160
161 #[allow(non_upper_case_globals)]
162 pub(crate) fn set_underline(&mut self, range: CFRange, underline: bool) {
163 const kCTUnderlineStyleNone: i32 = 0x00;
164 const kCTUnderlineStyleSingle: i32 = 0x01;
165
166 let value = if underline {
167 kCTUnderlineStyleSingle
168 } else {
169 kCTUnderlineStyleNone
170 };
171 unsafe {
172 self.inner.set_attribute(
173 range,
174 string_attributes::kCTUnderlineStyleAttributeName,
175 &CFNumber::from(value).as_CFType(),
176 )
177 }
178 }
179
180 pub(crate) fn set_fg_color(&mut self, range: CFRange, color: Color) {
181 let (r, g, b, a) = color.as_rgba();
182 let color = CGColor::rgb(r, g, b, a);
183 unsafe {
184 self.inner.set_attribute(
185 range,
186 string_attributes::kCTForegroundColorAttributeName,
187 &color.as_CFType(),
188 )
189 }
190 }
191
192 pub(crate) fn range(&self) -> CFRange {
193 CFRange::init(0, self.inner.char_len())
194 }
195}
196
197impl Framesetter {
198 pub(crate) fn new(attributed_string: &AttributedString) -> Self {
199 Framesetter(CTFramesetter::new_with_attributed_string(
200 attributed_string.inner.as_concrete_TypeRef(),
201 ))
202 }
203
204 #[allow(dead_code)]
206 pub(crate) fn suggest_frame_size(
207 &self,
208 range: CFRange,
209 constraints: CGSize,
210 ) -> (CGSize, CFRange) {
211 self.0
212 .suggest_frame_size_with_constraints(range, std::ptr::null(), constraints)
213 }
214
215 pub(crate) fn create_frame(&self, range: CFRange, path: &CGPathRef) -> Frame {
216 let frame = self.0.create_frame(range, path);
217 let lines = frame.get_lines().into_iter().map(Line);
218 Frame {
219 frame,
220 lines: lines.collect(),
221 }
222 }
223}
224
225impl Frame {
226 pub(crate) fn lines(&self) -> &[Line] {
227 &self.lines
228 }
229
230 pub(crate) fn get_line(&self, line_number: usize) -> Option<Line> {
231 self.lines.get(line_number).cloned()
232 }
233
234 pub(crate) fn get_line_origins(&self, range: CFRange) -> Vec<CGPoint> {
235 self.frame.get_line_origins(range)
236 }
237
238 #[allow(dead_code)]
239 pub(crate) fn draw(&self, ctx: &mut CGContextRef) {
240 self.frame.draw(ctx)
241 }
242}
243
244impl Line {
245 pub(crate) fn get_string_range(&self) -> CFRange {
246 self.0.get_string_range()
247 }
248
249 pub(crate) fn get_typographic_bounds(&self) -> TypographicBounds {
250 self.0.get_typographic_bounds()
251 }
252
253 pub(crate) fn get_trailing_whitespace_width(&self) -> f64 {
254 unsafe { CTLineGetTrailingWhitespaceWidth(self.0.as_concrete_TypeRef()) }
255 }
256
257 pub(crate) fn get_image_bounds(&self) -> Rect {
258 unsafe {
259 let r = CTLineGetImageBounds(self.0.as_concrete_TypeRef(), std::ptr::null_mut());
260 Rect::from_origin_size((r.origin.x, r.origin.y), (r.size.width, r.size.height))
261 }
262 }
263
264 pub(crate) fn draw(&self, ctx: &mut CGContextRef) {
265 unsafe { CTLineDraw(self.0.as_concrete_TypeRef(), ctx.as_ptr()) }
266 }
267
268 pub(crate) fn get_string_index_for_position(&self, position: CGPoint) -> CFIndex {
269 self.0.get_string_index_for_position(position)
270 }
271
272 pub(crate) fn get_offset_for_string_index(&self, index: CFIndex) -> CGFloat {
280 self.0.get_string_offset_for_string_index(index)
281 }
282}
283
284pub(crate) fn ct_family_name(family: &FontFamily, size: f64) -> CFString {
287 match &family.inner() {
288 FontFamilyInner::Named(name) => CFString::new(name),
289 other => system_font_family_name(other, size),
290 }
291}
292
293fn system_font_family_name(family: &FontFamilyInner, size: f64) -> CFString {
295 let font = system_font_impl(family, size).unwrap_or_else(create_font_comma_never_fail_period);
296 unsafe {
297 let name = CTFontCopyName(font.as_concrete_TypeRef(), kCTFontFamilyNameKey);
298 CFString::wrap_under_create_rule(name)
299 }
300}
301
302fn system_font_impl(family: &FontFamilyInner, size: f64) -> Option<CTFont> {
303 match family {
304 FontFamilyInner::SansSerif | FontFamilyInner::SystemUi => {
305 create_system_font(kCTFontSystemFontType, size)
306 }
307 FontFamilyInner::Serif => font::new_from_name("Charter", size)
310 .or_else(|_| font::new_from_name("Times", size))
311 .or_else(|_| font::new_from_name("Times New Roman", size))
312 .ok(),
313 FontFamilyInner::Monospace => font::new_from_name("Menlo", size)
315 .ok()
316 .or_else(|| create_system_font(kCTFontUserFixedPitchFontType, size)),
317 _ => panic!("system fontz only"),
318 }
319}
320
321fn create_system_font(typ: CTFontUIFontType, size: f64) -> Option<CTFont> {
322 unsafe {
323 let font = CTFontCreateUIFontForLanguage(typ, size, std::ptr::null());
324 if font.is_null() {
325 None
326 } else {
327 Some(CTFont::wrap_under_create_rule(font))
328 }
329 }
330}
331
332fn create_font_comma_never_fail_period() -> CTFont {
333 let empty_attributes = CFDictionary::from_CFType_pairs(&[]);
334 let descriptor = font_descriptor::new_from_attributes(&empty_attributes);
335 font::new_from_descriptor(&descriptor, 0.0)
336}
337
338pub(crate) fn add_font(font_data: &[u8]) -> Result<String, ()> {
339 unsafe {
340 let data = CGDataProvider::from_slice(font_data);
341 let font_ref = CGFont::from_data_provider(data)?;
342 let success = CTFontManagerRegisterGraphicsFont(font_ref.as_ptr(), std::ptr::null_mut());
343 if success {
344 let ct_font = font::new_from_CGFont(&font_ref, 0.0);
345 Ok(ct_font.family_name())
346 } else {
347 Err(())
348 }
349 }
350}
351
352impl FontCollection {
354 pub(crate) fn new_with_all_fonts() -> FontCollection {
355 FontCollection(font_collection::create_for_all_families())
356 }
357
358 pub(crate) fn font_for_family_name(&mut self, name: &str) -> Option<FontFamily> {
359 let name = CFString::from(name);
360 unsafe {
361 let array = CTFontCollectionCreateMatchingFontDescriptorsForFamily(
362 self.0.as_concrete_TypeRef(),
363 name.as_concrete_TypeRef(),
364 std::ptr::null(),
365 );
366
367 if array.is_null() {
368 None
369 } else {
370 let array = CFArray::<CTFontDescriptor>::wrap_under_create_rule(array);
371 array
372 .get(0)
373 .map(|desc| FontFamily::new_unchecked(desc.family_name()))
374 }
375 }
376 }
377}
378
379#[allow(clippy::many_single_char_names)]
381pub(crate) fn make_font(desc: &CTFontDescriptor, pt_size: f64, affine: Affine) -> CTFont {
382 let [a, b, c, d, e, f] = affine.as_coeffs();
383 let affine = CGAffineTransform::new(a, b, c, d, e, f);
384 unsafe {
385 let font_ref = CTFontCreateWithFontDescriptor(
386 desc.as_concrete_TypeRef(),
387 pt_size as CGFloat,
388 &affine as *const _,
391 );
392 CTFont::wrap_under_create_rule(font_ref)
393 }
394}
395
396#[link(name = "CoreText", kind = "framework")]
397unsafe extern "C" {
398 static kCTFontFamilyNameKey: CFStringRef;
399
400 pub static kCTFontVariationAxisIdentifierKey: CFStringRef;
401 fn CTFontCreateUIFontForLanguage(
407 font_type: CTFontUIFontType,
408 size: CGFloat,
409 language: CFStringRef,
410 ) -> CTFontRef;
411 fn CTParagraphStyleGetTypeID() -> CFTypeID;
412 fn CTParagraphStyleCreate(
413 settings: *const CTParagraphStyleSetting,
414 count: usize,
415 ) -> CTParagraphStyleRef;
416 fn CTLineGetImageBounds(line: CTLineRef, ctx: *mut c_void) -> CGRect;
417 fn CTLineDraw(line: CTLineRef, ctx: core_graphics::sys::CGContextRef);
418 fn CTLineGetTrailingWhitespaceWidth(line: CTLineRef) -> f64;
419 fn CTFontCollectionCreateMatchingFontDescriptorsForFamily(
420 collection: CTFontCollectionRef,
421 family: CFStringRef,
422 option: CFDictionaryRef,
423 ) -> CFArrayRef;
424 fn CTFontCopyName(font: CTFontRef, nameKey: CFStringRef) -> CFStringRef;
425 fn CTFontCreateWithFontDescriptor(
426 descriptor: CTFontDescriptorRef,
427 size: CGFloat,
428 matrix: *const CGAffineTransform,
429 ) -> CTFontRef;
430 fn CTFontManagerRegisterGraphicsFont(
431 font: core_graphics::sys::CGFontRef,
432 error: *mut c_void,
433 ) -> bool;
434}