Ports the macOS-specific Zig code behind cfg(target_os = "macos"): - renderer: a GraphicsApi trait with generic::Renderer<G>; Metal backend (all renderer/metal/* files, shaders.metal verbatim, runtime library compile, IOSurface layer, CVDisplayLink), shadertoy -> MSL via naga; OpenGL remains the backend elsewhere. - font: CoreText face, shaper and discovery with build.rs backend selection (coretext default; coretext-freetype / coretext-harfbuzz / coretext-noshape features), no fontconfig/freetype/harfbuzz on Apple. - input: KeymapDarwin via UCKeyTranslate, option-as-alt, mac keycodes. - os: macos, mach, cf_release_thread, NSProcessInfo args and macOS branches; config: macOS defaults, keybinds, Application Support paths. - termio/core: login(1) wrapping, openpty/execvpe, Darwin child exit handling, os_log logging. Compile-checked for aarch64/x86_64-apple-darwin (clippy -D warnings clean) using the cross setup in docs/rewrite/notes/macos-M1.md; cannot run here. The embedded runtime/C API (embedded.zig) is the next step. Also includes the cross-platform integration edits shared by the crash, verification, Windows and macOS work (Cargo target-gating, thread crash-context hooks, apprt-gtk surface/IME refactor, termio/os cfg gates), a cfg fix so ghostty-config builds on wasm32, and an allow for the intentional recursion in the crash overflow test. Test Plan: - cargo test --workspace (3224 passed) - cargo clippy --workspace --all-targets -- -D warnings - cargo check --target aarch64-apple-darwin -p ghostty-renderer (with the documented cross env)
166 lines
5.8 KiB
Rust
166 lines
5.8 KiB
Rust
//! Small helpers over the CoreText / CoreFoundation bindings shared by the
|
|
//! CoreText face, shaper and discovery (the Zig `macos` package conveniences
|
|
//! they rely on). Apple targets only.
|
|
#![allow(unsafe_code, clippy::trivially_copy_pass_by_ref)]
|
|
|
|
use std::{ops::Deref, ptr::NonNull};
|
|
|
|
use objc2_core_foundation::{CFData, CFRetained, CFString};
|
|
use objc2_core_text::{CTFont, CTFontTableOptions};
|
|
|
|
use crate::face::coretext_math::utf16_units;
|
|
|
|
/// A retained CoreFoundation object that may be moved/shared across threads.
|
|
///
|
|
/// CoreText font objects (`CTFont`, `CTFontDescriptor`) are immutable and
|
|
/// documented as thread safe by Apple ("CTFont ... are thread-safe"), but the
|
|
/// generated bindings conservatively do not mark them `Send`/`Sync`.
|
|
pub struct CfBox<T: ?Sized>(pub(crate) CFRetained<T>);
|
|
|
|
// SAFETY: see the type docs; the wrapped objects are immutable CoreText
|
|
// objects, safe to use from any thread.
|
|
unsafe impl<T: ?Sized> Send for CfBox<T> {}
|
|
// SAFETY: as above.
|
|
unsafe impl<T: ?Sized> Sync for CfBox<T> {}
|
|
|
|
impl<T: ?Sized> CfBox<T> {
|
|
pub(crate) fn new(v: CFRetained<T>) -> Self {
|
|
CfBox(v)
|
|
}
|
|
}
|
|
|
|
impl<T: ?Sized> Deref for CfBox<T> {
|
|
type Target = T;
|
|
fn deref(&self) -> &T {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl<T: objc2_core_foundation::Type> Clone for CfBox<T> {
|
|
fn clone(&self) -> Self {
|
|
CfBox(self.0.clone())
|
|
}
|
|
}
|
|
|
|
/// Convert a `CFString` to a Rust `String`.
|
|
pub(crate) fn cf_to_string(s: &CFString) -> String {
|
|
s.to_string()
|
|
}
|
|
|
|
/// Copy the sfnt table with the given tag, if present.
|
|
pub(crate) fn font_table(font: &CTFont, tag: &[u8; 4]) -> Option<CFRetained<CFData>> {
|
|
// SAFETY: plain CoreText call with valid arguments.
|
|
unsafe { font.table(u32::from_be_bytes(*tag), CTFontTableOptions::NoOptions) }
|
|
}
|
|
|
|
/// Copy the bytes of the sfnt table with the given tag, if present.
|
|
pub(crate) fn font_table_bytes(font: &CTFont, tag: &[u8; 4]) -> Option<Vec<u8>> {
|
|
font_table(font, tag).map(|d| d.to_vec())
|
|
}
|
|
|
|
/// `CTFontGetGlyphsForCharacters` for a single UTF-32 codepoint. Returns the
|
|
/// glyph ID of the first UTF-16 unit and whether *all* units mapped to glyphs
|
|
/// (the C function's return value).
|
|
pub(crate) fn glyphs_for_codepoint(font: &CTFont, cp: u32) -> (u16, bool) {
|
|
// Turn UTF-32 into UTF-16 for the CT API
|
|
let (mut unichars, len) = utf16_units(cp);
|
|
let mut glyphs = [0u16; 2];
|
|
// SAFETY: both buffers have at least `len` (<= 2) elements and outlive
|
|
// the call.
|
|
let ok = unsafe {
|
|
font.glyphs_for_characters(
|
|
NonNull::new_unchecked(unichars.as_mut_ptr()),
|
|
NonNull::new_unchecked(glyphs.as_mut_ptr()),
|
|
len as isize,
|
|
)
|
|
};
|
|
(glyphs[0], ok)
|
|
}
|
|
|
|
/// A font variation axis as reported by CoreText.
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub(crate) struct Axis {
|
|
pub name: String,
|
|
pub id: u32,
|
|
pub min: f64,
|
|
pub max: f64,
|
|
pub default: f64,
|
|
}
|
|
|
|
/// The variation axes of a font (`kCTFontVariationAxesAttribute`), or `None`
|
|
/// if the font has none.
|
|
pub(crate) fn variation_axes(font: &CTFont) -> Option<Vec<Axis>> {
|
|
use objc2_core_foundation::{CFArray, CFDictionary, CFNumber, CFType};
|
|
use objc2_core_text::{
|
|
kCTFontVariationAxisDefaultValueKey,
|
|
kCTFontVariationAxisIdentifierKey,
|
|
kCTFontVariationAxisMaximumValueKey,
|
|
kCTFontVariationAxisMinimumValueKey,
|
|
kCTFontVariationAxisNameKey,
|
|
};
|
|
|
|
// SAFETY: plain CoreText call.
|
|
let axes = unsafe { font.variation_axes() }?;
|
|
// SAFETY: the documented element type of the axes array is CFDictionary.
|
|
let axes: &CFArray<CFDictionary> = unsafe { axes.cast_unchecked() };
|
|
|
|
let mut out = Vec::with_capacity(axes.len());
|
|
for i in 0..axes.len() {
|
|
let Some(dict) = axes.get(i) else { continue };
|
|
// SAFETY: the values stored under these keys are CFNumber / CFString
|
|
// as documented for the variation axis keys.
|
|
let (names, nums) = unsafe {
|
|
(
|
|
dict.cast_unchecked::<CFString, CFString>(),
|
|
dict.cast_unchecked::<CFString, CFNumber>(),
|
|
)
|
|
};
|
|
// SAFETY: the keys are valid static CFStrings.
|
|
let (k_name, k_id, k_min, k_max, k_def) = unsafe {
|
|
(
|
|
kCTFontVariationAxisNameKey,
|
|
kCTFontVariationAxisIdentifierKey,
|
|
kCTFontVariationAxisMinimumValueKey,
|
|
kCTFontVariationAxisMaximumValueKey,
|
|
kCTFontVariationAxisDefaultValueKey,
|
|
)
|
|
};
|
|
let _ = std::marker::PhantomData::<CFType>;
|
|
let name = names.get(k_name).map(|s| s.to_string()).unwrap_or_default();
|
|
let id = nums
|
|
.get(k_id)
|
|
.and_then(|n| n.as_i32())
|
|
.map_or(0, |v| v as u32);
|
|
let min = nums.get(k_min).and_then(|n| n.as_f64()).unwrap_or(0.0);
|
|
let max = nums.get(k_max).and_then(|n| n.as_f64()).unwrap_or(0.0);
|
|
let default = nums.get(k_def).and_then(|n| n.as_f64()).unwrap_or(0.0);
|
|
out.push(Axis {
|
|
name,
|
|
id,
|
|
min,
|
|
max,
|
|
default,
|
|
});
|
|
}
|
|
Some(out)
|
|
}
|
|
|
|
/// The current value of each variation axis of the font instance
|
|
/// (`kCTFontVariationAttribute`), keyed by axis id.
|
|
pub(crate) fn variation_values(font: &CTFont) -> Option<std::collections::HashMap<u32, f64>> {
|
|
use objc2_core_foundation::{CFDictionary, CFNumber};
|
|
|
|
// SAFETY: plain CoreText call.
|
|
let values = unsafe { font.variation() }?;
|
|
// SAFETY: the variation dictionary maps CFNumber axis ids to CFNumber
|
|
// values.
|
|
let values: &CFDictionary<CFNumber, CFNumber> = unsafe { values.cast_unchecked() };
|
|
let (keys, vals) = values.to_vecs();
|
|
Some(
|
|
keys.iter()
|
|
.zip(vals.iter())
|
|
.filter_map(|(k, v)| Some((k.as_i32()? as u32, v.as_f64()?)))
|
|
.collect(),
|
|
)
|
|
}
|