#![allow(clippy::upper_case_acronyms)]
use std::ffi::c_void;
use std::rc::Rc;
use core_foundation::{
array::{CFArray, CFArrayRef, CFIndex},
attributed_string::CFMutableAttributedString,
base::{CFTypeID, TCFType},
declare_TCFType,
dictionary::{CFDictionary, CFDictionaryRef},
impl_TCFType,
number::CFNumber,
string::{CFString, CFStringRef},
};
use core_foundation_sys::base::CFRange;
use core_graphics::{
base::CGFloat,
color::CGColor,
context::CGContextRef,
data_provider::CGDataProvider,
font::CGFont,
geometry::{CGAffineTransform, CGPoint, CGRect, CGSize},
path::CGPathRef,
};
use core_text::{
font::{
self, kCTFontSystemFontType, kCTFontUserFixedPitchFontType, CTFont, CTFontRef,
CTFontUIFontType,
},
font_collection::{self, CTFontCollection, CTFontCollectionRef},
font_descriptor::{self, CTFontDescriptor, CTFontDescriptorRef},
frame::CTFrame,
framesetter::CTFramesetter,
line::{CTLine, CTLineRef, TypographicBounds},
string_attributes,
};
use foreign_types::{ForeignType, ForeignTypeRef};
use piet::kurbo::{Affine, Rect};
use piet::{util, Color, FontFamily, FontFamilyInner, TextAlignment};
#[derive(Clone)]
pub(crate) struct AttributedString {
pub(crate) inner: CFMutableAttributedString,
rtl: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct Framesetter(CTFramesetter);
#[derive(Debug, Clone)]
pub(crate) struct Frame {
frame: CTFrame,
lines: Rc<[Line]>,
}
#[derive(Debug, Clone)]
pub(crate) struct Line(CTLine);
#[derive(Debug, Clone)]
pub(crate) struct FontCollection(CTFontCollection);
pub enum __CTParagraphStyle {}
type CTParagraphStyleRef = *const __CTParagraphStyle;
declare_TCFType!(CTParagraphStyle, CTParagraphStyleRef);
impl_TCFType!(
CTParagraphStyle,
CTParagraphStyleRef,
CTParagraphStyleGetTypeID
);
#[repr(u32)]
enum CTParagraphStyleSpecifier {
Alignment = 0,
}
#[repr(u8)]
enum CTTextAlignment {
Left = 0,
Right = 1,
Center = 2,
Justified = 3,
Natural = 4,
}
#[repr(C)]
struct CTParagraphStyleSetting {
spec: CTParagraphStyleSpecifier,
value_size: usize,
value: *const c_void,
}
impl CTParagraphStyleSetting {
fn alignment(alignment: TextAlignment, is_rtl: bool) -> Self {
static LEFT: CTTextAlignment = CTTextAlignment::Left;
static RIGHT: CTTextAlignment = CTTextAlignment::Right;
static CENTER: CTTextAlignment = CTTextAlignment::Center;
static JUSTIFIED: CTTextAlignment = CTTextAlignment::Justified;
static NATURAL: CTTextAlignment = CTTextAlignment::Natural;
let alignment: *const CTTextAlignment = match alignment {
TextAlignment::Start => &NATURAL,
TextAlignment::End if is_rtl => &LEFT,
TextAlignment::End => &RIGHT,
TextAlignment::Center => &CENTER,
TextAlignment::Justified => &JUSTIFIED,
};
CTParagraphStyleSetting {
spec: CTParagraphStyleSpecifier::Alignment,
value: alignment as *const c_void,
value_size: std::mem::size_of::<CTTextAlignment>(),
}
}
}
impl AttributedString {
pub(crate) fn new(text: &str) -> Self {
let mut inner = CFMutableAttributedString::new();
let range = CFRange::init(0, 0);
let cf_string = CFString::new(text);
inner.replace_str(&cf_string, range);
let rtl = util::first_strong_rtl(text);
AttributedString { inner, rtl }
}
pub(crate) fn set_alignment(&mut self, alignment: TextAlignment) {
let alignment = CTParagraphStyleSetting::alignment(alignment, self.rtl);
let settings = [alignment];
unsafe {
let style = CTParagraphStyleCreate(settings.as_ptr(), 1);
let style = CTParagraphStyle::wrap_under_create_rule(style);
self.inner.set_attribute(
self.range(),
string_attributes::kCTParagraphStyleAttributeName,
&style,
);
}
}
pub(crate) fn set_font(&mut self, range: CFRange, font: &CTFont) {
unsafe {
self.inner
.set_attribute(range, string_attributes::kCTFontAttributeName, font);
}
}
#[allow(non_upper_case_globals)]
pub(crate) fn set_underline(&mut self, range: CFRange, underline: bool) {
const kCTUnderlineStyleNone: i32 = 0x00;
const kCTUnderlineStyleSingle: i32 = 0x01;
let value = if underline {
kCTUnderlineStyleSingle
} else {
kCTUnderlineStyleNone
};
unsafe {
self.inner.set_attribute(
range,
string_attributes::kCTUnderlineStyleAttributeName,
&CFNumber::from(value).as_CFType(),
)
}
}
pub(crate) fn set_fg_color(&mut self, range: CFRange, color: Color) {
let (r, g, b, a) = color.as_rgba();
let color = CGColor::rgb(r, g, b, a);
unsafe {
self.inner.set_attribute(
range,
string_attributes::kCTForegroundColorAttributeName,
&color.as_CFType(),
)
}
}
pub(crate) fn range(&self) -> CFRange {
CFRange::init(0, self.inner.char_len())
}
}
impl Framesetter {
pub(crate) fn new(attributed_string: &AttributedString) -> Self {
Framesetter(CTFramesetter::new_with_attributed_string(
attributed_string.inner.as_concrete_TypeRef(),
))
}
#[allow(dead_code)]
pub(crate) fn suggest_frame_size(
&self,
range: CFRange,
constraints: CGSize,
) -> (CGSize, CFRange) {
self.0
.suggest_frame_size_with_constraints(range, std::ptr::null(), constraints)
}
pub(crate) fn create_frame(&self, range: CFRange, path: &CGPathRef) -> Frame {
let frame = self.0.create_frame(range, path);
let lines = frame.get_lines().into_iter().map(Line);
Frame {
frame,
lines: lines.collect(),
}
}
}
impl Frame {
pub(crate) fn lines(&self) -> &[Line] {
&self.lines
}
pub(crate) fn get_line(&self, line_number: usize) -> Option<Line> {
self.lines.get(line_number).cloned()
}
pub(crate) fn get_line_origins(&self, range: CFRange) -> Vec<CGPoint> {
self.frame.get_line_origins(range)
}
#[allow(dead_code)]
pub(crate) fn draw(&self, ctx: &mut CGContextRef) {
self.frame.draw(ctx)
}
}
impl Line {
pub(crate) fn get_string_range(&self) -> CFRange {
self.0.get_string_range()
}
pub(crate) fn get_typographic_bounds(&self) -> TypographicBounds {
self.0.get_typographic_bounds()
}
pub(crate) fn get_trailing_whitespace_width(&self) -> f64 {
unsafe { CTLineGetTrailingWhitespaceWidth(self.0.as_concrete_TypeRef()) }
}
pub(crate) fn get_image_bounds(&self) -> Rect {
unsafe {
let r = CTLineGetImageBounds(self.0.as_concrete_TypeRef(), std::ptr::null_mut());
Rect::from_origin_size((r.origin.x, r.origin.y), (r.size.width, r.size.height))
}
}
pub(crate) fn draw(&self, ctx: &mut CGContextRef) {
unsafe { CTLineDraw(self.0.as_concrete_TypeRef(), ctx.as_ptr()) }
}
pub(crate) fn get_string_index_for_position(&self, position: CGPoint) -> CFIndex {
self.0.get_string_index_for_position(position)
}
pub(crate) fn get_offset_for_string_index(&self, index: CFIndex) -> CGFloat {
self.0.get_string_offset_for_string_index(index)
}
}
pub(crate) fn ct_family_name(family: &FontFamily, size: f64) -> CFString {
match &family.inner() {
FontFamilyInner::Named(name) => CFString::new(name),
other => system_font_family_name(other, size),
}
}
fn system_font_family_name(family: &FontFamilyInner, size: f64) -> CFString {
let font = system_font_impl(family, size).unwrap_or_else(create_font_comma_never_fail_period);
unsafe {
let name = CTFontCopyName(font.as_concrete_TypeRef(), kCTFontFamilyNameKey);
CFString::wrap_under_create_rule(name)
}
}
fn system_font_impl(family: &FontFamilyInner, size: f64) -> Option<CTFont> {
match family {
FontFamilyInner::SansSerif | FontFamilyInner::SystemUi => {
create_system_font(kCTFontSystemFontType, size)
}
FontFamilyInner::Serif => font::new_from_name("Charter", size)
.or_else(|_| font::new_from_name("Times", size))
.or_else(|_| font::new_from_name("Times New Roman", size))
.ok(),
FontFamilyInner::Monospace => font::new_from_name("Menlo", size)
.ok()
.or_else(|| create_system_font(kCTFontUserFixedPitchFontType, size)),
_ => panic!("system fontz only"),
}
}
fn create_system_font(typ: CTFontUIFontType, size: f64) -> Option<CTFont> {
unsafe {
let font = CTFontCreateUIFontForLanguage(typ, size, std::ptr::null());
if font.is_null() {
None
} else {
Some(CTFont::wrap_under_create_rule(font))
}
}
}
fn create_font_comma_never_fail_period() -> CTFont {
let empty_attributes = CFDictionary::from_CFType_pairs(&[]);
let descriptor = font_descriptor::new_from_attributes(&empty_attributes);
font::new_from_descriptor(&descriptor, 0.0)
}
pub(crate) fn add_font(font_data: &[u8]) -> Result<String, ()> {
unsafe {
let data = CGDataProvider::from_slice(font_data);
let font_ref = CGFont::from_data_provider(data)?;
let success = CTFontManagerRegisterGraphicsFont(font_ref.as_ptr(), std::ptr::null_mut());
if success {
let ct_font = font::new_from_CGFont(&font_ref, 0.0);
Ok(ct_font.family_name())
} else {
Err(())
}
}
}
impl FontCollection {
pub(crate) fn new_with_all_fonts() -> FontCollection {
FontCollection(font_collection::create_for_all_families())
}
pub(crate) fn font_for_family_name(&mut self, name: &str) -> Option<FontFamily> {
let name = CFString::from(name);
unsafe {
let array = CTFontCollectionCreateMatchingFontDescriptorsForFamily(
self.0.as_concrete_TypeRef(),
name.as_concrete_TypeRef(),
std::ptr::null(),
);
if array.is_null() {
None
} else {
let array = CFArray::<CTFontDescriptor>::wrap_under_create_rule(array);
array
.get(0)
.map(|desc| FontFamily::new_unchecked(desc.family_name()))
}
}
}
}
#[allow(clippy::many_single_char_names)]
pub(crate) fn make_font(desc: &CTFontDescriptor, pt_size: f64, affine: Affine) -> CTFont {
let [a, b, c, d, e, f] = affine.as_coeffs();
let affine = CGAffineTransform::new(a, b, c, d, e, f);
unsafe {
let font_ref = CTFontCreateWithFontDescriptor(
desc.as_concrete_TypeRef(),
pt_size as CGFloat,
&affine as *const _,
);
CTFont::wrap_under_create_rule(font_ref)
}
}
#[link(name = "CoreText", kind = "framework")]
extern "C" {
static kCTFontFamilyNameKey: CFStringRef;
pub static kCTFontVariationAxisIdentifierKey: CFStringRef;
fn CTFontCreateUIFontForLanguage(
font_type: CTFontUIFontType,
size: CGFloat,
language: CFStringRef,
) -> CTFontRef;
fn CTParagraphStyleGetTypeID() -> CFTypeID;
fn CTParagraphStyleCreate(
settings: *const CTParagraphStyleSetting,
count: usize,
) -> CTParagraphStyleRef;
fn CTLineGetImageBounds(line: CTLineRef, ctx: *mut c_void) -> CGRect;
fn CTLineDraw(line: CTLineRef, ctx: core_graphics::sys::CGContextRef);
fn CTLineGetTrailingWhitespaceWidth(line: CTLineRef) -> f64;
fn CTFontCollectionCreateMatchingFontDescriptorsForFamily(
collection: CTFontCollectionRef,
family: CFStringRef,
option: CFDictionaryRef,
) -> CFArrayRef;
fn CTFontCopyName(font: CTFontRef, nameKey: CFStringRef) -> CFStringRef;
fn CTFontCreateWithFontDescriptor(
descriptor: CTFontDescriptorRef,
size: CGFloat,
matrix: *const CGAffineTransform,
) -> CTFontRef;
fn CTFontManagerRegisterGraphicsFont(
font: core_graphics::sys::CGFontRef,
error: *mut c_void,
) -> bool;
}