Skip to content

Commit 6932d5f

Browse files
authored
Mac core lib (#1307)
2 parents 974fa44 + aa3d16e commit 6932d5f

14 files changed

Lines changed: 1059 additions & 0 deletions

File tree

Cargo.lock

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ members = [
5353
"crates/errors",
5454
"crates/geturl",
5555
"crates/io_base",
56+
"crates/mac_core",
5657
"crates/pdf_io",
5758
"crates/status_base",
5859
"crates/xdv",

crates/mac_core/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
lints.workspace = true
2+
3+
[package]
4+
name = "tectonic_mac_core"
5+
license = "MIT"
6+
version = "0.0.0-dev.0"
7+
edition = "2021"
8+
9+
[dependencies]
10+
libc = "0.2"
11+
12+
[build-dependencies]
13+
tectonic_cfg_support = { path = "../cfg_support", version = "0.0.0-dev.0" }
14+
15+
[package.metadata.internal_dep_versions]
16+
tectonic_cfg_support = "thiscommit:aeRoo7oa"

crates/mac_core/build.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//! Mac core library build script. Links to framework libraries when targeting MacOS.
2+
3+
use tectonic_cfg_support::target_cfg;
4+
5+
fn main() {
6+
let is_mac_os = target_cfg!(target_os = "macos");
7+
8+
if is_mac_os {
9+
println!("cargo:rustc-link-lib=framework=Foundation");
10+
println!("cargo:rustc-link-lib=framework=CoreFoundation");
11+
println!("cargo:rustc-link-lib=framework=CoreGraphics");
12+
println!("cargo:rustc-link-lib=framework=CoreText");
13+
println!("cargo:rustc-link-lib=framework=AppKit");
14+
}
15+
}

crates/mac_core/src/array.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use super::{sys, CoreType};
2+
use std::marker::PhantomData;
3+
use std::ops::Index;
4+
use std::ptr;
5+
use std::ptr::NonNull;
6+
7+
cfty! {
8+
/// A homogeneous array of CFType values, similar to [`Vec`].
9+
CFArray<T> : CFArrayGetTypeID
10+
}
11+
12+
impl<T: CoreType> CFArray<T> {
13+
/// Create a new, empty [`CFArray`].
14+
pub fn empty() -> CFArray<T> {
15+
// SAFETY: Valid to call with all null and zero length + default callbacks
16+
let ptr = unsafe {
17+
sys::CFArrayCreate(
18+
ptr::null_mut(),
19+
ptr::null_mut(),
20+
0,
21+
&sys::kCFTypeArrayCallBacks,
22+
)
23+
};
24+
let ptr = NonNull::new(ptr.cast_mut()).unwrap();
25+
// SAFETY: If non-null, pointer from CFArrayCreate is a new, owned CFArray.
26+
unsafe { CFArray::new_owned(ptr) }
27+
}
28+
29+
/// Create a new [`CFArray`] that contains the provided values.
30+
pub fn new(values: &[T]) -> CFArray<T> {
31+
// SAFETY: Length matches provided slice and values are `CoreType` so must be valid
32+
// CFTypeRefs.
33+
let ptr = unsafe {
34+
sys::CFArrayCreate(
35+
ptr::null_mut(),
36+
values.as_ptr().cast_mut().cast(),
37+
values.len() as sys::CFIndex,
38+
&sys::kCFTypeArrayCallBacks,
39+
)
40+
};
41+
let ptr = NonNull::new(ptr.cast_mut()).unwrap();
42+
// SAFETY: If non-null, pointer from CFCArrayCreate is a new, owned CFArray.
43+
unsafe { CFArray::new_owned(ptr) }
44+
}
45+
46+
/// Get the length of this array
47+
pub fn len(&self) -> usize {
48+
// SAFETY: Internal pointer is guaranteed valid.
49+
unsafe { sys::CFArrayGetCount(self.0.as_ptr()) as usize }
50+
}
51+
52+
/// Check whether this array is empty
53+
pub fn is_empty(&self) -> bool {
54+
self.len() == 0
55+
}
56+
}
57+
58+
impl<T: CoreType> Index<usize> for CFArray<T> {
59+
type Output = T;
60+
61+
fn index(&self, index: usize) -> &Self::Output {
62+
if index >= self.len() {
63+
panic!("Index {index} out of bounds for CFArray");
64+
}
65+
// SAFETY: Internal pointer is guaranteed valid. Index has been verified in-bounds.
66+
let ptr =
67+
unsafe { sys::CFArrayGetValueAtIndex(self.0.cast().as_ptr(), index as sys::CFIndex) }
68+
.cast::<T>();
69+
// SAFETY: API contracts ensure all values are of the correct type and live for our lifetime.
70+
unsafe { &*ptr }
71+
}
72+
}

crates/mac_core/src/dict.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use super::{sys, CoreType};
2+
use std::marker::PhantomData;
3+
use std::mem::MaybeUninit;
4+
use std::ptr::NonNull;
5+
use std::{mem, ptr};
6+
7+
pub trait AsPtr {
8+
fn as_ptr(&self) -> *const ();
9+
#[allow(clippy::len_without_is_empty)]
10+
fn len(&self) -> usize;
11+
}
12+
13+
impl<T> AsPtr for Vec<T> {
14+
fn as_ptr(&self) -> *const () {
15+
self.as_ptr().cast()
16+
}
17+
18+
fn len(&self) -> usize {
19+
self.len()
20+
}
21+
}
22+
23+
impl<T, const N: usize> AsPtr for [T; N] {
24+
fn as_ptr(&self) -> *const () {
25+
<[T]>::as_ptr(self).cast()
26+
}
27+
28+
fn len(&self) -> usize {
29+
N
30+
}
31+
}
32+
33+
pub trait Pairs {
34+
type Keys: AsPtr;
35+
type Values: AsPtr;
36+
37+
fn into_pairs(self) -> (Self::Keys, Self::Values);
38+
}
39+
40+
impl<K, V, const N: usize> Pairs for [(K, V); N] {
41+
type Keys = [K; N];
42+
type Values = [V; N];
43+
44+
fn into_pairs(self) -> (Self::Keys, Self::Values) {
45+
let mut keys: [MaybeUninit<K>; N] = [const { MaybeUninit::uninit() }; N];
46+
let mut values: [MaybeUninit<V>; N] = [const { MaybeUninit::uninit() }; N];
47+
48+
for (idx, (key, val)) in self.into_iter().enumerate() {
49+
keys[idx].write(key);
50+
values[idx].write(val);
51+
}
52+
53+
// SAFETY: All values in both arrays have been initialized
54+
unsafe {
55+
(
56+
mem::transmute_copy::<_, [K; N]>(&keys),
57+
mem::transmute_copy::<_, [V; N]>(&values),
58+
)
59+
}
60+
}
61+
}
62+
63+
impl<K, V> Pairs for Vec<(K, V)> {
64+
type Keys = Vec<K>;
65+
type Values = Vec<V>;
66+
67+
fn into_pairs(self) -> (Self::Keys, Self::Values) {
68+
self.into_iter().unzip()
69+
}
70+
}
71+
72+
cfty! {
73+
/// A dictionary / map of CFType keys to values, similar to [`HashMap`](std::collections::HashMap)
74+
CFDictionary<K, V> : CFDictionaryGetTypeID
75+
}
76+
77+
impl<K: CoreType, V: CoreType> CFDictionary<K, V> {
78+
/// Create a new [`CFDictionary`] that contains the provided key/value pairs.
79+
pub fn new<P: Pairs>(pairs: P) -> CFDictionary<K, V> {
80+
let (keys, values) = pairs.into_pairs();
81+
82+
// SAFETY: Length matches provided slices and values are `CoreType` so must be valid
83+
// CFTypeRefs.
84+
let ptr = unsafe {
85+
sys::CFDictionaryCreate(
86+
ptr::null_mut(),
87+
keys.as_ptr().cast_mut().cast(),
88+
values.as_ptr().cast_mut().cast(),
89+
keys.len() as sys::CFIndex,
90+
&sys::kCFTypeDictionaryKeyCallBacks,
91+
&sys::kCFTypeDictionaryValueCallBacks,
92+
)
93+
};
94+
let ptr = NonNull::new(ptr.cast_mut()).unwrap();
95+
// SAFETY: If non-null, pointer from CFDictionaryCreate is a new, owned CFDictionary.
96+
unsafe { CFDictionary::new_owned(ptr) }
97+
}
98+
}

crates/mac_core/src/font.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use super::{sys, CFString, CFType, CTFontDescriptor, CoreType};
2+
use std::ptr;
3+
use std::ptr::NonNull;
4+
5+
/// An attribute that may be found in a font, such as family name or URL.
6+
#[derive(Copy, Clone)]
7+
pub enum FontAttribute {
8+
/// Name
9+
Name,
10+
/// Family Name
11+
FamilyName,
12+
/// Display Name
13+
DisplayName,
14+
/// URL
15+
URL,
16+
/// Cascade List - fallbacks to use if glyph not present in this font.
17+
CascadeList,
18+
}
19+
20+
impl FontAttribute {
21+
/// Convert this attribute into its matching [`CFString`].
22+
pub fn to_str(self) -> CFString {
23+
let ptr = NonNull::new(self.to_raw().cast_mut()).unwrap();
24+
// SAFETY: The value returned by `to_raw` is guaranteed to be a valid CFStringRef
25+
unsafe { CFString::new_borrowed(ptr) }
26+
}
27+
28+
/// Convert this attribute into a raw pointer to a [`CFString`].
29+
pub fn to_raw(self) -> sys::CFStringRef {
30+
match self {
31+
// SAFETY: Static guaranteed to exist and by a valid CFStringRef
32+
FontAttribute::Name => unsafe { sys::kCTFontNameAttribute },
33+
// SAFETY: sic
34+
FontAttribute::FamilyName => unsafe { sys::kCTFontFamilyNameAttribute },
35+
// SAFETY: sic
36+
FontAttribute::DisplayName => unsafe { sys::kCTFontDisplayNameAttribute },
37+
// SAFETY: sic
38+
FontAttribute::URL => unsafe { sys::kCTFontURLAttribute },
39+
// SAFETY: sic
40+
FontAttribute::CascadeList => unsafe { sys::kCTFontCascadeListAttribute },
41+
}
42+
}
43+
}
44+
45+
/// A type of font name that may be available.
46+
#[derive(Copy, Clone)]
47+
pub enum FontNameKey {
48+
/// Full name
49+
Full,
50+
/// Family name
51+
Family,
52+
/// Style name
53+
Style,
54+
/// PostScript name
55+
PostScript,
56+
}
57+
58+
impl FontNameKey {
59+
/// Convert this attribute into its matching [`CFString`].
60+
pub fn to_str(self) -> CFString {
61+
let ptr = NonNull::new(self.to_raw().cast_mut()).unwrap();
62+
// SAFETY: The value returned by `to_raw` is guaranteed to be a valid CFStringRef
63+
unsafe { CFString::new_borrowed(ptr) }
64+
}
65+
66+
fn to_raw(self) -> sys::CFStringRef {
67+
match self {
68+
// SAFETY: Static guaranteed to exist and by a valid CFStringRef
69+
FontNameKey::Full => unsafe { sys::kCTFontFullNameKey },
70+
// SAFETY: sic
71+
FontNameKey::Family => unsafe { sys::kCTFontFamilyNameKey },
72+
// SAFETY: sic
73+
FontNameKey::Style => unsafe { sys::kCTFontStyleNameKey },
74+
// SAFETY: sic
75+
FontNameKey::PostScript => unsafe { sys::kCTFontPostScriptNameKey },
76+
}
77+
}
78+
}
79+
80+
cfty! {
81+
/// A font, combining a descriptor and a size, as well as any other necessary transforms to
82+
/// render glyphs.
83+
CTFont : CTFontGetTypeID
84+
}
85+
86+
impl CTFont {
87+
/// Create a new font from a [`CTFontDescriptor`] and a size to render at.
88+
pub fn new_descriptor(descriptor: &CTFontDescriptor, size: f64) -> CTFont {
89+
// SAFETY: Provided descriptor is guaranteed valid. Any size will work. Null matrix is always
90+
// allowed.
91+
let ptr = unsafe {
92+
sys::CTFontCreateWithFontDescriptor(
93+
descriptor.as_type_ref(),
94+
size as sys::CGFloat,
95+
ptr::null_mut(),
96+
)
97+
};
98+
let ptr = NonNull::new(ptr.cast_mut()).unwrap();
99+
// SAFETY: If non-null, pointer from CTFontCreateWithFontDescriptor is a new, owned CTFont.
100+
unsafe { CTFont::new_owned(ptr) }
101+
}
102+
103+
/// Get an attribute of this font, if present
104+
pub fn attr(&self, attr: FontAttribute) -> Option<CFType> {
105+
// SAFETY: Internal pointer and attribute string guaranteed valid.
106+
let ptr = unsafe { sys::CTFontCopyAttribute(self.as_type_ref(), attr.to_raw()) };
107+
// SAFETY: In non-null, returned name guaranteed valid and owned.
108+
NonNull::new(ptr.cast_mut()).map(|ptr| unsafe { CFType::new_owned(ptr) })
109+
}
110+
111+
/// Get a name value of this font, if present
112+
pub fn name(&self, name: FontNameKey) -> Option<CFString> {
113+
// SAFETY: Internal pointer and name string guaranteed valid.
114+
let ptr = unsafe { sys::CTFontCopyName(self.as_type_ref(), name.to_raw()) };
115+
// SAFETY: In non-null, returned name guaranteed valid and owned.
116+
NonNull::new(ptr.cast_mut()).map(|ptr| unsafe { CFString::new_owned(ptr) })
117+
}
118+
119+
/// Get the name of this font, localized to a specific language
120+
pub fn localized_name(&self, name: FontNameKey) -> Option<CFString> {
121+
// SAFETY: Internal pointer and name string guaranteed valid.
122+
let ptr = unsafe {
123+
sys::CTFontCopyLocalizedName(self.as_type_ref(), name.to_raw(), ptr::null_mut())
124+
};
125+
// SAFETY: In non-null, returned name guaranteed valid and owned.
126+
NonNull::new(ptr.cast_mut()).map(|ptr| unsafe { CFString::new_owned(ptr) })
127+
}
128+
}

0 commit comments

Comments
 (0)