Remove 2 suffix from gpui
Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
3c81dda8e2
commit
f5ba22659b
225 changed files with 8511 additions and 41063 deletions
|
@ -1,173 +0,0 @@
|
|||
use crate::geometry::{
|
||||
rect::RectI,
|
||||
vector::{vec2i, Vector2I},
|
||||
};
|
||||
use etagere::BucketedAtlasAllocator;
|
||||
use foreign_types::ForeignType;
|
||||
use log::warn;
|
||||
use metal::{Device, TextureDescriptor};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
pub struct AtlasAllocator {
|
||||
device: Device,
|
||||
texture_descriptor: TextureDescriptor,
|
||||
atlases: Vec<Atlas>,
|
||||
last_used_atlas_id: usize,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct AllocId {
|
||||
pub atlas_id: usize,
|
||||
alloc_id: etagere::AllocId,
|
||||
}
|
||||
|
||||
impl AtlasAllocator {
|
||||
pub fn new(device: Device, texture_descriptor: TextureDescriptor) -> Self {
|
||||
let mut this = Self {
|
||||
device,
|
||||
texture_descriptor,
|
||||
atlases: vec![],
|
||||
last_used_atlas_id: 0,
|
||||
};
|
||||
let atlas = this.new_atlas(Vector2I::zero());
|
||||
this.atlases.push(atlas);
|
||||
this
|
||||
}
|
||||
|
||||
pub fn default_atlas_size(&self) -> Vector2I {
|
||||
vec2i(
|
||||
self.texture_descriptor.width() as i32,
|
||||
self.texture_descriptor.height() as i32,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn allocate(&mut self, requested_size: Vector2I) -> Option<(AllocId, Vector2I)> {
|
||||
let atlas_id = self.last_used_atlas_id;
|
||||
if let Some((alloc_id, origin)) = self.atlases[atlas_id].allocate(requested_size) {
|
||||
return Some((AllocId { atlas_id, alloc_id }, origin));
|
||||
}
|
||||
|
||||
for (atlas_id, atlas) in self.atlases.iter_mut().enumerate() {
|
||||
if atlas_id == self.last_used_atlas_id {
|
||||
continue;
|
||||
}
|
||||
if let Some((alloc_id, origin)) = atlas.allocate(requested_size) {
|
||||
self.last_used_atlas_id = atlas_id;
|
||||
return Some((AllocId { atlas_id, alloc_id }, origin));
|
||||
}
|
||||
}
|
||||
|
||||
let atlas_id = self.atlases.len();
|
||||
let mut atlas = self.new_atlas(requested_size);
|
||||
let allocation = atlas
|
||||
.allocate(requested_size)
|
||||
.map(|(alloc_id, origin)| (AllocId { atlas_id, alloc_id }, origin));
|
||||
self.atlases.push(atlas);
|
||||
|
||||
if allocation.is_none() {
|
||||
warn!(
|
||||
"allocation of size {:?} could not be created",
|
||||
requested_size,
|
||||
);
|
||||
}
|
||||
|
||||
allocation
|
||||
}
|
||||
|
||||
pub fn upload(&mut self, size: Vector2I, bytes: &[u8]) -> Option<(AllocId, RectI)> {
|
||||
let (alloc_id, origin) = self.allocate(size)?;
|
||||
let bounds = RectI::new(origin, size);
|
||||
self.atlases[alloc_id.atlas_id].upload(bounds, bytes);
|
||||
Some((alloc_id, bounds))
|
||||
}
|
||||
|
||||
pub fn deallocate(&mut self, id: AllocId) {
|
||||
if let Some(atlas) = self.atlases.get_mut(id.atlas_id) {
|
||||
atlas.deallocate(id.alloc_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
for atlas in &mut self.atlases {
|
||||
atlas.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
|
||||
self.atlases.get(atlas_id).map(|a| a.texture.as_ref())
|
||||
}
|
||||
|
||||
fn new_atlas(&mut self, required_size: Vector2I) -> Atlas {
|
||||
let size = self.default_atlas_size().max(required_size);
|
||||
let texture = if size.x() as u64 > self.texture_descriptor.width()
|
||||
|| size.y() as u64 > self.texture_descriptor.height()
|
||||
{
|
||||
let descriptor = unsafe {
|
||||
let descriptor_ptr: *mut metal::MTLTextureDescriptor =
|
||||
msg_send![self.texture_descriptor, copy];
|
||||
metal::TextureDescriptor::from_ptr(descriptor_ptr)
|
||||
};
|
||||
descriptor.set_width(size.x() as u64);
|
||||
descriptor.set_height(size.y() as u64);
|
||||
|
||||
self.device.new_texture(&descriptor)
|
||||
} else {
|
||||
self.device.new_texture(&self.texture_descriptor)
|
||||
};
|
||||
Atlas::new(size, texture)
|
||||
}
|
||||
}
|
||||
|
||||
struct Atlas {
|
||||
allocator: BucketedAtlasAllocator,
|
||||
texture: metal::Texture,
|
||||
}
|
||||
|
||||
impl Atlas {
|
||||
fn new(size: Vector2I, texture: metal::Texture) -> Self {
|
||||
Self {
|
||||
allocator: BucketedAtlasAllocator::new(etagere::Size::new(size.x(), size.y())),
|
||||
texture,
|
||||
}
|
||||
}
|
||||
|
||||
fn allocate(&mut self, size: Vector2I) -> Option<(etagere::AllocId, Vector2I)> {
|
||||
let alloc = self
|
||||
.allocator
|
||||
.allocate(etagere::Size::new(size.x(), size.y()))?;
|
||||
let origin = alloc.rectangle.min;
|
||||
Some((alloc.id, vec2i(origin.x, origin.y)))
|
||||
}
|
||||
|
||||
fn upload(&mut self, bounds: RectI, bytes: &[u8]) {
|
||||
let region = metal::MTLRegion::new_2d(
|
||||
bounds.origin().x() as u64,
|
||||
bounds.origin().y() as u64,
|
||||
bounds.size().x() as u64,
|
||||
bounds.size().y() as u64,
|
||||
);
|
||||
self.texture.replace_region(
|
||||
region,
|
||||
0,
|
||||
bytes.as_ptr() as *const _,
|
||||
(bounds.size().x() * self.bytes_per_pixel() as i32) as u64,
|
||||
);
|
||||
}
|
||||
|
||||
fn bytes_per_pixel(&self) -> u8 {
|
||||
use metal::MTLPixelFormat::*;
|
||||
match self.texture.pixel_format() {
|
||||
A8Unorm | R8Unorm => 1,
|
||||
RGBA8Unorm | BGRA8Unorm => 4,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn deallocate(&mut self, id: etagere::AllocId) {
|
||||
self.allocator.deallocate(id);
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.allocator.clear();
|
||||
}
|
||||
}
|
|
@ -2,15 +2,16 @@
|
|||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::{PlatformDispatcher, TaskLabel};
|
||||
use async_task::Runnable;
|
||||
use objc::{
|
||||
class, msg_send,
|
||||
runtime::{BOOL, YES},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use std::ffi::c_void;
|
||||
|
||||
use crate::platform;
|
||||
use parking::{Parker, Unparker};
|
||||
use parking_lot::Mutex;
|
||||
use std::{ffi::c_void, sync::Arc, time::Duration};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
|
||||
|
||||
|
@ -18,15 +19,41 @@ pub fn dispatch_get_main_queue() -> dispatch_queue_t {
|
|||
unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
|
||||
}
|
||||
|
||||
pub struct Dispatcher;
|
||||
pub struct MacDispatcher {
|
||||
parker: Arc<Mutex<Parker>>,
|
||||
}
|
||||
|
||||
impl platform::Dispatcher for Dispatcher {
|
||||
impl Default for MacDispatcher {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MacDispatcher {
|
||||
pub fn new() -> Self {
|
||||
MacDispatcher {
|
||||
parker: Arc::new(Mutex::new(Parker::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformDispatcher for MacDispatcher {
|
||||
fn is_main_thread(&self) -> bool {
|
||||
let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
|
||||
is_main_thread == YES
|
||||
}
|
||||
|
||||
fn run_on_main_thread(&self, runnable: Runnable) {
|
||||
fn dispatch(&self, runnable: Runnable, _: Option<TaskLabel>) {
|
||||
unsafe {
|
||||
dispatch_async_f(
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0),
|
||||
runnable.into_raw() as *mut c_void,
|
||||
Some(trampoline),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_on_main_thread(&self, runnable: Runnable) {
|
||||
unsafe {
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
|
@ -34,10 +61,36 @@ impl platform::Dispatcher for Dispatcher {
|
|||
Some(trampoline),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn trampoline(runnable: *mut c_void) {
|
||||
let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
|
||||
task.run();
|
||||
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
|
||||
unsafe {
|
||||
let queue =
|
||||
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0);
|
||||
let when = dispatch_time(DISPATCH_TIME_NOW as u64, duration.as_nanos() as i64);
|
||||
dispatch_after_f(
|
||||
when,
|
||||
queue,
|
||||
runnable.into_raw() as *mut c_void,
|
||||
Some(trampoline),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(&self, _background_only: bool) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn park(&self) {
|
||||
self.parker.lock().park()
|
||||
}
|
||||
|
||||
fn unparker(&self) -> Unparker {
|
||||
self.parker.lock().unparker()
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn trampoline(runnable: *mut c_void) {
|
||||
let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
|
||||
task.run();
|
||||
}
|
||||
|
|
143
crates/gpui/src/platform/mac/display.rs
Normal file
143
crates/gpui/src/platform/mac/display.rs
Normal file
|
@ -0,0 +1,143 @@
|
|||
use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay};
|
||||
use anyhow::Result;
|
||||
use core_foundation::uuid::{CFUUIDGetUUIDBytes, CFUUIDRef};
|
||||
use core_graphics::{
|
||||
display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList},
|
||||
geometry::{CGPoint, CGRect, CGSize},
|
||||
};
|
||||
use std::any::Any;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MacDisplay(pub(crate) CGDirectDisplayID);
|
||||
|
||||
unsafe impl Send for MacDisplay {}
|
||||
|
||||
impl MacDisplay {
|
||||
/// Get the screen with the given [DisplayId].
|
||||
pub fn find_by_id(id: DisplayId) -> Option<Self> {
|
||||
Self::all().find(|screen| screen.id() == id)
|
||||
}
|
||||
|
||||
/// Get the screen with the given persistent [Uuid].
|
||||
pub fn find_by_uuid(uuid: Uuid) -> Option<Self> {
|
||||
Self::all().find(|screen| screen.uuid().ok() == Some(uuid))
|
||||
}
|
||||
|
||||
/// Get the primary screen - the one with the menu bar, and whose bottom left
|
||||
/// corner is at the origin of the AppKit coordinate system.
|
||||
pub fn primary() -> Self {
|
||||
Self::all().next().unwrap()
|
||||
}
|
||||
|
||||
/// Obtains an iterator over all currently active system displays.
|
||||
pub fn all() -> impl Iterator<Item = Self> {
|
||||
unsafe {
|
||||
let mut display_count: u32 = 0;
|
||||
let result = CGGetActiveDisplayList(0, std::ptr::null_mut(), &mut display_count);
|
||||
|
||||
if result == 0 {
|
||||
let mut displays = Vec::with_capacity(display_count as usize);
|
||||
CGGetActiveDisplayList(display_count, displays.as_mut_ptr(), &mut display_count);
|
||||
displays.set_len(display_count as usize);
|
||||
|
||||
displays.into_iter().map(MacDisplay)
|
||||
} else {
|
||||
panic!("Failed to get active display list");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
}
|
||||
|
||||
/// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space.
|
||||
///
|
||||
/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
|
||||
/// with the Y axis pointing upwards.
|
||||
///
|
||||
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
||||
/// screen, with the Y axis pointing downwards.
|
||||
pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
|
||||
let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size;
|
||||
|
||||
Bounds {
|
||||
origin: point(
|
||||
GlobalPixels(rect.origin.x as f32),
|
||||
GlobalPixels(
|
||||
primary_screen_size.height as f32 - rect.origin.y as f32 - rect.size.height as f32,
|
||||
),
|
||||
),
|
||||
size: size(
|
||||
GlobalPixels(rect.size.width as f32),
|
||||
GlobalPixels(rect.size.height as f32),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the given rectangle from GPUI's coordinate system to CoreGraphics' native coordinate space.
|
||||
///
|
||||
/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
|
||||
/// with the Y axis pointing upwards.
|
||||
///
|
||||
/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
|
||||
/// screen, with the Y axis pointing downwards.
|
||||
pub(crate) fn display_bounds_to_native(bounds: Bounds<GlobalPixels>) -> CGRect {
|
||||
let primary_screen_height = MacDisplay::primary().bounds().size.height;
|
||||
|
||||
CGRect::new(
|
||||
&CGPoint::new(
|
||||
bounds.origin.x.into(),
|
||||
(primary_screen_height - bounds.origin.y - bounds.size.height).into(),
|
||||
),
|
||||
&CGSize::new(bounds.size.width.into(), bounds.size.height.into()),
|
||||
)
|
||||
}
|
||||
|
||||
impl PlatformDisplay for MacDisplay {
|
||||
fn id(&self) -> DisplayId {
|
||||
DisplayId(self.0)
|
||||
}
|
||||
|
||||
fn uuid(&self) -> Result<Uuid> {
|
||||
let cfuuid = unsafe { CGDisplayCreateUUIDFromDisplayID(self.0 as CGDirectDisplayID) };
|
||||
anyhow::ensure!(
|
||||
!cfuuid.is_null(),
|
||||
"AppKit returned a null from CGDisplayCreateUUIDFromDisplayID"
|
||||
);
|
||||
|
||||
let bytes = unsafe { CFUUIDGetUUIDBytes(cfuuid) };
|
||||
Ok(Uuid::from_bytes([
|
||||
bytes.byte0,
|
||||
bytes.byte1,
|
||||
bytes.byte2,
|
||||
bytes.byte3,
|
||||
bytes.byte4,
|
||||
bytes.byte5,
|
||||
bytes.byte6,
|
||||
bytes.byte7,
|
||||
bytes.byte8,
|
||||
bytes.byte9,
|
||||
bytes.byte10,
|
||||
bytes.byte11,
|
||||
bytes.byte12,
|
||||
bytes.byte13,
|
||||
bytes.byte14,
|
||||
bytes.byte15,
|
||||
]))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn bounds(&self) -> Bounds<GlobalPixels> {
|
||||
unsafe {
|
||||
let native_bounds = CGDisplayBounds(self.0);
|
||||
display_bounds_from_native(native_bounds)
|
||||
}
|
||||
}
|
||||
}
|
274
crates/gpui/src/platform/mac/display_linker.rs
Normal file
274
crates/gpui/src/platform/mac/display_linker.rs
Normal file
|
@ -0,0 +1,274 @@
|
|||
use std::{
|
||||
ffi::c_void,
|
||||
mem,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
|
||||
use crate::DisplayId;
|
||||
use collections::HashMap;
|
||||
use parking_lot::Mutex;
|
||||
pub use sys::CVSMPTETime as SmtpeTime;
|
||||
pub use sys::CVTimeStamp as VideoTimestamp;
|
||||
|
||||
pub(crate) struct MacDisplayLinker {
|
||||
links: HashMap<DisplayId, MacDisplayLink>,
|
||||
}
|
||||
|
||||
struct MacDisplayLink {
|
||||
system_link: sys::DisplayLink,
|
||||
_output_callback: Arc<OutputCallback>,
|
||||
}
|
||||
|
||||
impl MacDisplayLinker {
|
||||
pub fn new() -> Self {
|
||||
MacDisplayLinker {
|
||||
links: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type OutputCallback = Mutex<Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>>;
|
||||
|
||||
impl MacDisplayLinker {
|
||||
pub fn set_output_callback(
|
||||
&mut self,
|
||||
display_id: DisplayId,
|
||||
output_callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp) + Send>,
|
||||
) {
|
||||
if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } {
|
||||
let callback = Arc::new(Mutex::new(output_callback));
|
||||
let weak_callback_ptr: *const OutputCallback = Arc::downgrade(&callback).into_raw();
|
||||
unsafe { system_link.set_output_callback(trampoline, weak_callback_ptr as *mut c_void) }
|
||||
|
||||
self.links.insert(
|
||||
display_id,
|
||||
MacDisplayLink {
|
||||
_output_callback: callback,
|
||||
system_link,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
log::warn!("DisplayLink could not be obtained for {:?}", display_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self, display_id: DisplayId) {
|
||||
if let Some(link) = self.links.get_mut(&display_id) {
|
||||
unsafe {
|
||||
link.system_link.start();
|
||||
}
|
||||
} else {
|
||||
log::warn!("No DisplayLink callback registered for {:?}", display_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop(&mut self, display_id: DisplayId) {
|
||||
if let Some(link) = self.links.get_mut(&display_id) {
|
||||
unsafe {
|
||||
link.system_link.stop();
|
||||
}
|
||||
} else {
|
||||
log::warn!("No DisplayLink callback registered for {:?}", display_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn trampoline(
|
||||
_display_link_out: *mut sys::CVDisplayLink,
|
||||
current_time: *const sys::CVTimeStamp,
|
||||
output_time: *const sys::CVTimeStamp,
|
||||
_flags_in: i64,
|
||||
_flags_out: *mut i64,
|
||||
user_data: *mut c_void,
|
||||
) -> i32 {
|
||||
if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
|
||||
let output_callback: Weak<OutputCallback> =
|
||||
Weak::from_raw(user_data as *mut OutputCallback);
|
||||
if let Some(output_callback) = output_callback.upgrade() {
|
||||
(output_callback.lock())(current_time, output_time)
|
||||
}
|
||||
mem::forget(output_callback);
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
mod sys {
|
||||
//! Derived from display-link crate under the fololwing license:
|
||||
//! https://github.com/BrainiumLLC/display-link/blob/master/LICENSE-MIT
|
||||
//! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc)
|
||||
#![allow(dead_code, non_upper_case_globals)]
|
||||
|
||||
use foreign_types::{foreign_type, ForeignType};
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
fmt::{Debug, Formatter, Result},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CVDisplayLink {}
|
||||
|
||||
foreign_type! {
|
||||
type CType = CVDisplayLink;
|
||||
fn drop = CVDisplayLinkRelease;
|
||||
fn clone = CVDisplayLinkRetain;
|
||||
pub struct DisplayLink;
|
||||
pub struct DisplayLinkRef;
|
||||
}
|
||||
|
||||
impl Debug for DisplayLink {
|
||||
fn fmt(&self, formatter: &mut Formatter) -> Result {
|
||||
formatter
|
||||
.debug_tuple("DisplayLink")
|
||||
.field(&self.as_ptr())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CVTimeStamp {
|
||||
pub version: u32,
|
||||
pub video_time_scale: i32,
|
||||
pub video_time: i64,
|
||||
pub host_time: u64,
|
||||
pub rate_scalar: f64,
|
||||
pub video_refresh_period: i64,
|
||||
pub smpte_time: CVSMPTETime,
|
||||
pub flags: u64,
|
||||
pub reserved: u64,
|
||||
}
|
||||
|
||||
pub type CVTimeStampFlags = u64;
|
||||
|
||||
pub const kCVTimeStampVideoTimeValid: CVTimeStampFlags = 1 << 0;
|
||||
pub const kCVTimeStampHostTimeValid: CVTimeStampFlags = 1 << 1;
|
||||
pub const kCVTimeStampSMPTETimeValid: CVTimeStampFlags = 1 << 2;
|
||||
pub const kCVTimeStampVideoRefreshPeriodValid: CVTimeStampFlags = 1 << 3;
|
||||
pub const kCVTimeStampRateScalarValid: CVTimeStampFlags = 1 << 4;
|
||||
pub const kCVTimeStampTopField: CVTimeStampFlags = 1 << 16;
|
||||
pub const kCVTimeStampBottomField: CVTimeStampFlags = 1 << 17;
|
||||
pub const kCVTimeStampVideoHostTimeValid: CVTimeStampFlags =
|
||||
kCVTimeStampVideoTimeValid | kCVTimeStampHostTimeValid;
|
||||
pub const kCVTimeStampIsInterlaced: CVTimeStampFlags =
|
||||
kCVTimeStampTopField | kCVTimeStampBottomField;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct CVSMPTETime {
|
||||
pub subframes: i16,
|
||||
pub subframe_divisor: i16,
|
||||
pub counter: u32,
|
||||
pub time_type: u32,
|
||||
pub flags: u32,
|
||||
pub hours: i16,
|
||||
pub minutes: i16,
|
||||
pub seconds: i16,
|
||||
pub frames: i16,
|
||||
}
|
||||
|
||||
pub type CVSMPTETimeType = u32;
|
||||
|
||||
pub const kCVSMPTETimeType24: CVSMPTETimeType = 0;
|
||||
pub const kCVSMPTETimeType25: CVSMPTETimeType = 1;
|
||||
pub const kCVSMPTETimeType30Drop: CVSMPTETimeType = 2;
|
||||
pub const kCVSMPTETimeType30: CVSMPTETimeType = 3;
|
||||
pub const kCVSMPTETimeType2997: CVSMPTETimeType = 4;
|
||||
pub const kCVSMPTETimeType2997Drop: CVSMPTETimeType = 5;
|
||||
pub const kCVSMPTETimeType60: CVSMPTETimeType = 6;
|
||||
pub const kCVSMPTETimeType5994: CVSMPTETimeType = 7;
|
||||
|
||||
pub type CVSMPTETimeFlags = u32;
|
||||
|
||||
pub const kCVSMPTETimeValid: CVSMPTETimeFlags = 1 << 0;
|
||||
pub const kCVSMPTETimeRunning: CVSMPTETimeFlags = 1 << 1;
|
||||
|
||||
pub type CVDisplayLinkOutputCallback = unsafe extern "C" fn(
|
||||
display_link_out: *mut CVDisplayLink,
|
||||
// A pointer to the current timestamp. This represents the timestamp when the callback is called.
|
||||
current_time: *const CVTimeStamp,
|
||||
// A pointer to the output timestamp. This represents the timestamp for when the frame will be displayed.
|
||||
output_time: *const CVTimeStamp,
|
||||
// Unused
|
||||
flags_in: i64,
|
||||
// Unused
|
||||
flags_out: *mut i64,
|
||||
// A pointer to app-defined data.
|
||||
display_link_context: *mut c_void,
|
||||
) -> i32;
|
||||
|
||||
#[link(name = "CoreFoundation", kind = "framework")]
|
||||
#[link(name = "CoreVideo", kind = "framework")]
|
||||
#[allow(improper_ctypes)]
|
||||
extern "C" {
|
||||
pub fn CVDisplayLinkCreateWithActiveCGDisplays(
|
||||
display_link_out: *mut *mut CVDisplayLink,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkCreateWithCGDisplay(
|
||||
display_id: u32,
|
||||
display_link_out: *mut *mut CVDisplayLink,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkSetOutputCallback(
|
||||
display_link: &mut DisplayLinkRef,
|
||||
callback: CVDisplayLinkOutputCallback,
|
||||
user_info: *mut c_void,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkSetCurrentCGDisplay(
|
||||
display_link: &mut DisplayLinkRef,
|
||||
display_id: u32,
|
||||
) -> i32;
|
||||
pub fn CVDisplayLinkStart(display_link: &mut DisplayLinkRef) -> i32;
|
||||
pub fn CVDisplayLinkStop(display_link: &mut DisplayLinkRef) -> i32;
|
||||
pub fn CVDisplayLinkRelease(display_link: *mut CVDisplayLink);
|
||||
pub fn CVDisplayLinkRetain(display_link: *mut CVDisplayLink) -> *mut CVDisplayLink;
|
||||
}
|
||||
|
||||
impl DisplayLink {
|
||||
/// Apple docs: [CVDisplayLinkCreateWithActiveCGDisplays](https://developer.apple.com/documentation/corevideo/1456863-cvdisplaylinkcreatewithactivecgd?language=objc)
|
||||
pub unsafe fn new() -> Option<Self> {
|
||||
let mut display_link: *mut CVDisplayLink = 0 as _;
|
||||
let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
|
||||
if code == 0 {
|
||||
Some(DisplayLink::from_ptr(display_link))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc)
|
||||
pub unsafe fn on_display(display_id: u32) -> Option<Self> {
|
||||
let mut display_link: *mut CVDisplayLink = 0 as _;
|
||||
let code = CVDisplayLinkCreateWithCGDisplay(display_id, &mut display_link);
|
||||
if code == 0 {
|
||||
Some(DisplayLink::from_ptr(display_link))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayLinkRef {
|
||||
/// Apple docs: [CVDisplayLinkSetOutputCallback](https://developer.apple.com/documentation/corevideo/1457096-cvdisplaylinksetoutputcallback?language=objc)
|
||||
pub unsafe fn set_output_callback(
|
||||
&mut self,
|
||||
callback: CVDisplayLinkOutputCallback,
|
||||
user_info: *mut c_void,
|
||||
) {
|
||||
assert_eq!(CVDisplayLinkSetOutputCallback(self, callback, user_info), 0);
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkSetCurrentCGDisplay](https://developer.apple.com/documentation/corevideo/1456768-cvdisplaylinksetcurrentcgdisplay?language=objc)
|
||||
pub unsafe fn set_current_display(&mut self, display_id: u32) {
|
||||
assert_eq!(CVDisplayLinkSetCurrentCGDisplay(self, display_id), 0);
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc)
|
||||
pub unsafe fn start(&mut self) {
|
||||
assert_eq!(CVDisplayLinkStart(self), 0);
|
||||
}
|
||||
|
||||
/// Apple docs: [CVDisplayLinkStop](https://developer.apple.com/documentation/corevideo/1457281-cvdisplaylinkstop?language=objc)
|
||||
pub unsafe fn stop(&mut self) {
|
||||
assert_eq!(CVDisplayLinkStop(self), 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,7 @@
|
|||
use crate::{
|
||||
geometry::vector::vec2f,
|
||||
keymap_matcher::Keystroke,
|
||||
platform::{
|
||||
Event, KeyDownEvent, KeyUpEvent, Modifiers, ModifiersChangedEvent, MouseButton,
|
||||
MouseButtonEvent, MouseExitedEvent, MouseMovedEvent, NavigationDirection, ScrollDelta,
|
||||
ScrollWheelEvent, TouchPhase,
|
||||
},
|
||||
point, px, InputEvent, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
|
||||
MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection,
|
||||
Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase,
|
||||
};
|
||||
use cocoa::{
|
||||
appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
|
||||
|
@ -71,23 +67,23 @@ pub fn key_to_native(key: &str) -> Cow<str> {
|
|||
|
||||
unsafe fn read_modifiers(native_event: id) -> Modifiers {
|
||||
let modifiers = native_event.modifierFlags();
|
||||
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||
let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
|
||||
let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
|
||||
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||
let fun = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
|
||||
let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
|
||||
|
||||
Modifiers {
|
||||
ctrl,
|
||||
control,
|
||||
alt,
|
||||
shift,
|
||||
cmd,
|
||||
fun,
|
||||
command,
|
||||
function,
|
||||
}
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub unsafe fn from_native(native_event: id, window_height: Option<f32>) -> Option<Self> {
|
||||
impl InputEvent {
|
||||
pub unsafe fn from_native(native_event: id, window_height: Option<Pixels>) -> Option<Self> {
|
||||
let event_type = native_event.eventType();
|
||||
|
||||
// Filter out event types that aren't in the NSEventType enum.
|
||||
|
@ -123,16 +119,15 @@ impl Event {
|
|||
_ => return None,
|
||||
};
|
||||
window_height.map(|window_height| {
|
||||
Self::MouseDown(MouseButtonEvent {
|
||||
Self::MouseDown(MouseDownEvent {
|
||||
button,
|
||||
position: vec2f(
|
||||
native_event.locationInWindow().x as f32,
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
// MacOS screen coordinates are relative to bottom left
|
||||
window_height - native_event.locationInWindow().y as f32,
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
modifiers: read_modifiers(native_event),
|
||||
click_count: native_event.clickCount() as usize,
|
||||
is_down: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -150,46 +145,44 @@ impl Event {
|
|||
};
|
||||
|
||||
window_height.map(|window_height| {
|
||||
Self::MouseUp(MouseButtonEvent {
|
||||
Self::MouseUp(MouseUpEvent {
|
||||
button,
|
||||
position: vec2f(
|
||||
native_event.locationInWindow().x as f32,
|
||||
// MacOS view coordinates are relative to bottom left
|
||||
window_height - native_event.locationInWindow().y as f32,
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
modifiers: read_modifiers(native_event),
|
||||
click_count: native_event.clickCount() as usize,
|
||||
is_down: false,
|
||||
})
|
||||
})
|
||||
}
|
||||
NSEventType::NSScrollWheel => window_height.map(|window_height| {
|
||||
let phase = match native_event.phase() {
|
||||
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
|
||||
Some(TouchPhase::Started)
|
||||
TouchPhase::Started
|
||||
}
|
||||
NSEventPhase::NSEventPhaseEnded => Some(TouchPhase::Ended),
|
||||
_ => Some(TouchPhase::Moved),
|
||||
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
|
||||
_ => TouchPhase::Moved,
|
||||
};
|
||||
|
||||
let raw_data = vec2f(
|
||||
let raw_data = point(
|
||||
native_event.scrollingDeltaX() as f32,
|
||||
native_event.scrollingDeltaY() as f32,
|
||||
);
|
||||
|
||||
let delta = if native_event.hasPreciseScrollingDeltas() == YES {
|
||||
ScrollDelta::Pixels(raw_data)
|
||||
ScrollDelta::Pixels(raw_data.map(px))
|
||||
} else {
|
||||
ScrollDelta::Lines(raw_data)
|
||||
};
|
||||
|
||||
Self::ScrollWheel(ScrollWheelEvent {
|
||||
position: vec2f(
|
||||
native_event.locationInWindow().x as f32,
|
||||
window_height - native_event.locationInWindow().y as f32,
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
delta,
|
||||
phase,
|
||||
touch_phase: phase,
|
||||
modifiers: read_modifiers(native_event),
|
||||
})
|
||||
}),
|
||||
|
@ -207,32 +200,33 @@ impl Event {
|
|||
};
|
||||
|
||||
window_height.map(|window_height| {
|
||||
Self::MouseMoved(MouseMovedEvent {
|
||||
Self::MouseMove(MouseMoveEvent {
|
||||
pressed_button: Some(pressed_button),
|
||||
position: vec2f(
|
||||
native_event.locationInWindow().x as f32,
|
||||
window_height - native_event.locationInWindow().y as f32,
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
modifiers: read_modifiers(native_event),
|
||||
})
|
||||
})
|
||||
}
|
||||
NSEventType::NSMouseMoved => window_height.map(|window_height| {
|
||||
Self::MouseMoved(MouseMovedEvent {
|
||||
position: vec2f(
|
||||
native_event.locationInWindow().x as f32,
|
||||
window_height - native_event.locationInWindow().y as f32,
|
||||
Self::MouseMove(MouseMoveEvent {
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
pressed_button: None,
|
||||
modifiers: read_modifiers(native_event),
|
||||
})
|
||||
}),
|
||||
NSEventType::NSMouseExited => window_height.map(|window_height| {
|
||||
Self::MouseExited(MouseExitedEvent {
|
||||
position: vec2f(
|
||||
native_event.locationInWindow().x as f32,
|
||||
window_height - native_event.locationInWindow().y as f32,
|
||||
Self::MouseExited(MouseExitEvent {
|
||||
position: point(
|
||||
px(native_event.locationInWindow().x as f32),
|
||||
window_height - px(native_event.locationInWindow().y as f32),
|
||||
),
|
||||
|
||||
pressed_button: None,
|
||||
modifiers: read_modifiers(native_event),
|
||||
})
|
||||
|
@ -253,10 +247,10 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
|
|||
let first_char = chars_ignoring_modifiers.chars().next().map(|ch| ch as u16);
|
||||
let modifiers = native_event.modifierFlags();
|
||||
|
||||
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||
let control = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
|
||||
let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
|
||||
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||
let command = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask)
|
||||
&& first_char.map_or(true, |ch| {
|
||||
!(NSUpArrowFunctionKey..=NSModeSwitchFunctionKey).contains(&ch)
|
||||
|
@ -297,7 +291,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
|
|||
|
||||
// Honor ⌘ when Dvorak-QWERTY is used.
|
||||
let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), true, false);
|
||||
if cmd && chars_ignoring_modifiers_and_shift != chars_with_cmd {
|
||||
if command && chars_ignoring_modifiers_and_shift != chars_with_cmd {
|
||||
chars_ignoring_modifiers =
|
||||
chars_for_modified_key(native_event.keyCode(), true, shift);
|
||||
chars_ignoring_modifiers_and_shift = chars_with_cmd;
|
||||
|
@ -321,11 +315,13 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
|
|||
};
|
||||
|
||||
Keystroke {
|
||||
ctrl,
|
||||
alt,
|
||||
shift,
|
||||
cmd,
|
||||
function,
|
||||
modifiers: Modifiers {
|
||||
control,
|
||||
alt,
|
||||
shift,
|
||||
command,
|
||||
function,
|
||||
},
|
||||
key,
|
||||
ime_key: None,
|
||||
}
|
|
@ -1,671 +0,0 @@
|
|||
mod open_type;
|
||||
|
||||
use crate::{
|
||||
fonts::{Features, FontId, GlyphId, Metrics, Properties},
|
||||
geometry::{
|
||||
rect::{RectF, RectI},
|
||||
transform2d::Transform2F,
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
platform::{self, RasterizationOptions},
|
||||
text_layout::{Glyph, LineLayout, Run, RunStyle},
|
||||
};
|
||||
use cocoa::appkit::{CGFloat, CGPoint};
|
||||
use collections::HashMap;
|
||||
use core_foundation::{
|
||||
array::CFIndex,
|
||||
attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
|
||||
base::{CFRange, TCFType},
|
||||
string::CFString,
|
||||
};
|
||||
use core_graphics::{
|
||||
base::{kCGImageAlphaPremultipliedLast, CGGlyph},
|
||||
color_space::CGColorSpace,
|
||||
context::CGContext,
|
||||
};
|
||||
use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
|
||||
use font_kit::{
|
||||
handle::Handle, hinting::HintingOptions, source::SystemSource, sources::mem::MemSource,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kCGImageAlphaOnly: u32 = 7;
|
||||
|
||||
pub struct FontSystem(RwLock<FontSystemState>);
|
||||
|
||||
struct FontSystemState {
|
||||
memory_source: MemSource,
|
||||
system_source: SystemSource,
|
||||
fonts: Vec<font_kit::font::Font>,
|
||||
font_ids_by_postscript_name: HashMap<String, FontId>,
|
||||
postscript_names_by_font_id: HashMap<FontId, String>,
|
||||
}
|
||||
|
||||
impl FontSystem {
|
||||
pub fn new() -> Self {
|
||||
Self(RwLock::new(FontSystemState {
|
||||
memory_source: MemSource::empty(),
|
||||
system_source: SystemSource::new(),
|
||||
fonts: Vec::new(),
|
||||
font_ids_by_postscript_name: Default::default(),
|
||||
postscript_names_by_font_id: Default::default(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FontSystem {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl platform::FontSystem for FontSystem {
|
||||
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
|
||||
self.0.write().add_fonts(fonts)
|
||||
}
|
||||
|
||||
fn all_families(&self) -> Vec<String> {
|
||||
self.0
|
||||
.read()
|
||||
.system_source
|
||||
.all_families()
|
||||
.expect("core text should never return an error")
|
||||
}
|
||||
|
||||
fn load_family(&self, name: &str, features: &Features) -> anyhow::Result<Vec<FontId>> {
|
||||
self.0.write().load_family(name, features)
|
||||
}
|
||||
|
||||
fn select_font(&self, font_ids: &[FontId], properties: &Properties) -> anyhow::Result<FontId> {
|
||||
self.0.read().select_font(font_ids, properties)
|
||||
}
|
||||
|
||||
fn font_metrics(&self, font_id: FontId) -> Metrics {
|
||||
self.0.read().font_metrics(font_id)
|
||||
}
|
||||
|
||||
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF> {
|
||||
self.0.read().typographic_bounds(font_id, glyph_id)
|
||||
}
|
||||
|
||||
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F> {
|
||||
self.0.read().advance(font_id, glyph_id)
|
||||
}
|
||||
|
||||
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
|
||||
self.0.read().glyph_for_char(font_id, ch)
|
||||
}
|
||||
|
||||
fn rasterize_glyph(
|
||||
&self,
|
||||
font_id: FontId,
|
||||
font_size: f32,
|
||||
glyph_id: GlyphId,
|
||||
subpixel_shift: Vector2F,
|
||||
scale_factor: f32,
|
||||
options: RasterizationOptions,
|
||||
) -> Option<(RectI, Vec<u8>)> {
|
||||
self.0.read().rasterize_glyph(
|
||||
font_id,
|
||||
font_size,
|
||||
glyph_id,
|
||||
subpixel_shift,
|
||||
scale_factor,
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
fn layout_line(&self, text: &str, font_size: f32, runs: &[(usize, RunStyle)]) -> LineLayout {
|
||||
self.0.write().layout_line(text, font_size, runs)
|
||||
}
|
||||
|
||||
fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize> {
|
||||
self.0.read().wrap_line(text, font_id, font_size, width)
|
||||
}
|
||||
}
|
||||
|
||||
impl FontSystemState {
|
||||
fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
|
||||
self.memory_source.add_fonts(
|
||||
fonts
|
||||
.iter()
|
||||
.map(|bytes| Handle::from_memory(bytes.clone(), 0)),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_family(&mut self, name: &str, features: &Features) -> anyhow::Result<Vec<FontId>> {
|
||||
let mut font_ids = Vec::new();
|
||||
|
||||
let family = self
|
||||
.memory_source
|
||||
.select_family_by_name(name)
|
||||
.or_else(|_| self.system_source.select_family_by_name(name))?;
|
||||
for font in family.fonts() {
|
||||
let mut font = font.load()?;
|
||||
open_type::apply_features(&mut font, features);
|
||||
let font_id = FontId(self.fonts.len());
|
||||
font_ids.push(font_id);
|
||||
let postscript_name = font.postscript_name().unwrap();
|
||||
self.font_ids_by_postscript_name
|
||||
.insert(postscript_name.clone(), font_id);
|
||||
self.postscript_names_by_font_id
|
||||
.insert(font_id, postscript_name);
|
||||
self.fonts.push(font);
|
||||
}
|
||||
Ok(font_ids)
|
||||
}
|
||||
|
||||
fn select_font(&self, font_ids: &[FontId], properties: &Properties) -> anyhow::Result<FontId> {
|
||||
let candidates = font_ids
|
||||
.iter()
|
||||
.map(|font_id| self.fonts[font_id.0].properties())
|
||||
.collect::<Vec<_>>();
|
||||
let idx = font_kit::matching::find_best_match(&candidates, properties)?;
|
||||
Ok(font_ids[idx])
|
||||
}
|
||||
|
||||
fn font_metrics(&self, font_id: FontId) -> Metrics {
|
||||
self.fonts[font_id.0].metrics()
|
||||
}
|
||||
|
||||
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<RectF> {
|
||||
Ok(self.fonts[font_id.0].typographic_bounds(glyph_id)?)
|
||||
}
|
||||
|
||||
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Vector2F> {
|
||||
Ok(self.fonts[font_id.0].advance(glyph_id)?)
|
||||
}
|
||||
|
||||
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
|
||||
self.fonts[font_id.0].glyph_for_char(ch)
|
||||
}
|
||||
|
||||
fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId {
|
||||
let postscript_name = requested_font.postscript_name();
|
||||
if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) {
|
||||
*font_id
|
||||
} else {
|
||||
let font_id = FontId(self.fonts.len());
|
||||
self.font_ids_by_postscript_name
|
||||
.insert(postscript_name.clone(), font_id);
|
||||
self.postscript_names_by_font_id
|
||||
.insert(font_id, postscript_name);
|
||||
self.fonts
|
||||
.push(font_kit::font::Font::from_core_graphics_font(
|
||||
requested_font.copy_to_CGFont(),
|
||||
));
|
||||
font_id
|
||||
}
|
||||
}
|
||||
|
||||
fn is_emoji(&self, font_id: FontId) -> bool {
|
||||
self.postscript_names_by_font_id
|
||||
.get(&font_id)
|
||||
.map_or(false, |postscript_name| {
|
||||
postscript_name == "AppleColorEmoji"
|
||||
})
|
||||
}
|
||||
|
||||
fn rasterize_glyph(
|
||||
&self,
|
||||
font_id: FontId,
|
||||
font_size: f32,
|
||||
glyph_id: GlyphId,
|
||||
subpixel_shift: Vector2F,
|
||||
scale_factor: f32,
|
||||
options: RasterizationOptions,
|
||||
) -> Option<(RectI, Vec<u8>)> {
|
||||
let font = &self.fonts[font_id.0];
|
||||
let scale = Transform2F::from_scale(scale_factor);
|
||||
let glyph_bounds = font
|
||||
.raster_bounds(
|
||||
glyph_id,
|
||||
font_size,
|
||||
scale,
|
||||
HintingOptions::None,
|
||||
font_kit::canvas::RasterizationOptions::GrayscaleAa,
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
|
||||
None
|
||||
} else {
|
||||
// Make room for subpixel variants.
|
||||
let subpixel_padding = subpixel_shift.ceil().to_i32();
|
||||
let cx_bounds = RectI::new(
|
||||
glyph_bounds.origin(),
|
||||
glyph_bounds.size() + subpixel_padding,
|
||||
);
|
||||
|
||||
let mut bytes;
|
||||
let cx;
|
||||
match options {
|
||||
RasterizationOptions::Alpha => {
|
||||
bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize];
|
||||
cx = CGContext::create_bitmap_context(
|
||||
Some(bytes.as_mut_ptr() as *mut _),
|
||||
cx_bounds.width() as usize,
|
||||
cx_bounds.height() as usize,
|
||||
8,
|
||||
cx_bounds.width() as usize,
|
||||
&CGColorSpace::create_device_gray(),
|
||||
kCGImageAlphaOnly,
|
||||
);
|
||||
}
|
||||
RasterizationOptions::Bgra => {
|
||||
bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize];
|
||||
cx = CGContext::create_bitmap_context(
|
||||
Some(bytes.as_mut_ptr() as *mut _),
|
||||
cx_bounds.width() as usize,
|
||||
cx_bounds.height() as usize,
|
||||
8,
|
||||
cx_bounds.width() as usize * 4,
|
||||
&CGColorSpace::create_device_rgb(),
|
||||
kCGImageAlphaPremultipliedLast,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Move the origin to bottom left and account for scaling, this
|
||||
// makes drawing text consistent with the font-kit's raster_bounds.
|
||||
cx.translate(
|
||||
-glyph_bounds.origin_x() as CGFloat,
|
||||
(glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
|
||||
);
|
||||
cx.scale(scale_factor as CGFloat, scale_factor as CGFloat);
|
||||
|
||||
cx.set_allows_font_subpixel_positioning(true);
|
||||
cx.set_should_subpixel_position_fonts(true);
|
||||
cx.set_allows_font_subpixel_quantization(false);
|
||||
cx.set_should_subpixel_quantize_fonts(false);
|
||||
font.native_font()
|
||||
.clone_with_font_size(font_size as CGFloat)
|
||||
.draw_glyphs(
|
||||
&[glyph_id as CGGlyph],
|
||||
&[CGPoint::new(
|
||||
(subpixel_shift.x() / scale_factor) as CGFloat,
|
||||
(subpixel_shift.y() / scale_factor) as CGFloat,
|
||||
)],
|
||||
cx,
|
||||
);
|
||||
|
||||
if let RasterizationOptions::Bgra = options {
|
||||
// Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
|
||||
for pixel in bytes.chunks_exact_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
let a = pixel[3] as f32 / 255.;
|
||||
pixel[0] = (pixel[0] as f32 / a) as u8;
|
||||
pixel[1] = (pixel[1] as f32 / a) as u8;
|
||||
pixel[2] = (pixel[2] as f32 / a) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
Some((cx_bounds, bytes))
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_line(
|
||||
&mut self,
|
||||
text: &str,
|
||||
font_size: f32,
|
||||
runs: &[(usize, RunStyle)],
|
||||
) -> LineLayout {
|
||||
// Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
|
||||
let mut string = CFMutableAttributedString::new();
|
||||
{
|
||||
string.replace_str(&CFString::new(text), CFRange::init(0, 0));
|
||||
let utf16_line_len = string.char_len() as usize;
|
||||
|
||||
let last_run: RefCell<Option<(usize, FontId)>> = Default::default();
|
||||
let font_runs = runs
|
||||
.iter()
|
||||
.filter_map(|(len, style)| {
|
||||
let mut last_run = last_run.borrow_mut();
|
||||
if let Some((last_len, last_font_id)) = last_run.as_mut() {
|
||||
if style.font_id == *last_font_id {
|
||||
*last_len += *len;
|
||||
None
|
||||
} else {
|
||||
let result = (*last_len, *last_font_id);
|
||||
*last_len = *len;
|
||||
*last_font_id = style.font_id;
|
||||
Some(result)
|
||||
}
|
||||
} else {
|
||||
*last_run = Some((*len, style.font_id));
|
||||
None
|
||||
}
|
||||
})
|
||||
.chain(std::iter::from_fn(|| last_run.borrow_mut().take()));
|
||||
|
||||
let mut ix_converter = StringIndexConverter::new(text);
|
||||
for (run_len, font_id) in font_runs {
|
||||
let utf8_end = ix_converter.utf8_ix + run_len;
|
||||
let utf16_start = ix_converter.utf16_ix;
|
||||
|
||||
if utf16_start >= utf16_line_len {
|
||||
break;
|
||||
}
|
||||
|
||||
ix_converter.advance_to_utf8_ix(utf8_end);
|
||||
let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);
|
||||
|
||||
let cf_range =
|
||||
CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
|
||||
let font = &self.fonts[font_id.0];
|
||||
unsafe {
|
||||
string.set_attribute(
|
||||
cf_range,
|
||||
kCTFontAttributeName,
|
||||
&font.native_font().clone_with_font_size(font_size as f64),
|
||||
);
|
||||
}
|
||||
|
||||
if utf16_end == utf16_line_len {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
|
||||
let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
|
||||
|
||||
let mut runs = Vec::new();
|
||||
for run in line.glyph_runs().into_iter() {
|
||||
let attributes = run.attributes().unwrap();
|
||||
let font = unsafe {
|
||||
attributes
|
||||
.get(kCTFontAttributeName)
|
||||
.downcast::<CTFont>()
|
||||
.unwrap()
|
||||
};
|
||||
let font_id = self.id_for_native_font(font);
|
||||
|
||||
let mut ix_converter = StringIndexConverter::new(text);
|
||||
let mut glyphs = Vec::new();
|
||||
for ((glyph_id, position), glyph_utf16_ix) in run
|
||||
.glyphs()
|
||||
.iter()
|
||||
.zip(run.positions().iter())
|
||||
.zip(run.string_indices().iter())
|
||||
{
|
||||
let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
|
||||
ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
|
||||
glyphs.push(Glyph {
|
||||
id: *glyph_id as GlyphId,
|
||||
position: vec2f(position.x as f32, position.y as f32),
|
||||
index: ix_converter.utf8_ix,
|
||||
is_emoji: self.is_emoji(font_id),
|
||||
});
|
||||
}
|
||||
|
||||
runs.push(Run { font_id, glyphs })
|
||||
}
|
||||
|
||||
let typographic_bounds = line.get_typographic_bounds();
|
||||
LineLayout {
|
||||
width: typographic_bounds.width as f32,
|
||||
ascent: typographic_bounds.ascent as f32,
|
||||
descent: typographic_bounds.descent as f32,
|
||||
runs,
|
||||
font_size,
|
||||
len: text.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize> {
|
||||
let mut string = CFMutableAttributedString::new();
|
||||
string.replace_str(&CFString::new(text), CFRange::init(0, 0));
|
||||
let cf_range = CFRange::init(0, text.encode_utf16().count() as isize);
|
||||
let font = &self.fonts[font_id.0];
|
||||
unsafe {
|
||||
string.set_attribute(
|
||||
cf_range,
|
||||
kCTFontAttributeName,
|
||||
&font.native_font().clone_with_font_size(font_size as f64),
|
||||
);
|
||||
|
||||
let typesetter = CTTypesetterCreateWithAttributedString(string.as_concrete_TypeRef());
|
||||
let mut ix_converter = StringIndexConverter::new(text);
|
||||
let mut break_indices = Vec::new();
|
||||
while ix_converter.utf8_ix < text.len() {
|
||||
let utf16_len = CTTypesetterSuggestLineBreak(
|
||||
typesetter,
|
||||
ix_converter.utf16_ix as isize,
|
||||
width as f64,
|
||||
) as usize;
|
||||
ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len);
|
||||
if ix_converter.utf8_ix >= text.len() {
|
||||
break;
|
||||
}
|
||||
break_indices.push(ix_converter.utf8_ix as usize);
|
||||
}
|
||||
break_indices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct StringIndexConverter<'a> {
|
||||
text: &'a str,
|
||||
utf8_ix: usize,
|
||||
utf16_ix: usize,
|
||||
}
|
||||
|
||||
impl<'a> StringIndexConverter<'a> {
|
||||
fn new(text: &'a str) -> Self {
|
||||
Self {
|
||||
text,
|
||||
utf8_ix: 0,
|
||||
utf16_ix: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_to_utf8_ix(&mut self, utf8_target: usize) {
|
||||
for (ix, c) in self.text[self.utf8_ix..].char_indices() {
|
||||
if self.utf8_ix + ix >= utf8_target {
|
||||
self.utf8_ix += ix;
|
||||
return;
|
||||
}
|
||||
self.utf16_ix += c.len_utf16();
|
||||
}
|
||||
self.utf8_ix = self.text.len();
|
||||
}
|
||||
|
||||
fn advance_to_utf16_ix(&mut self, utf16_target: usize) {
|
||||
for (ix, c) in self.text[self.utf8_ix..].char_indices() {
|
||||
if self.utf16_ix >= utf16_target {
|
||||
self.utf8_ix += ix;
|
||||
return;
|
||||
}
|
||||
self.utf16_ix += c.len_utf16();
|
||||
}
|
||||
self.utf8_ix = self.text.len();
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct __CFTypesetter(c_void);
|
||||
|
||||
pub type CTTypesetterRef = *const __CFTypesetter;
|
||||
|
||||
#[link(name = "CoreText", kind = "framework")]
|
||||
extern "C" {
|
||||
fn CTTypesetterCreateWithAttributedString(string: CFAttributedStringRef) -> CTTypesetterRef;
|
||||
|
||||
fn CTTypesetterSuggestLineBreak(
|
||||
typesetter: CTTypesetterRef,
|
||||
start_index: CFIndex,
|
||||
width: f64,
|
||||
) -> CFIndex;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::AppContext;
|
||||
use font_kit::properties::{Style, Weight};
|
||||
use platform::FontSystem as _;
|
||||
|
||||
#[crate::test(self, retries = 5)]
|
||||
fn test_layout_str(_: &mut AppContext) {
|
||||
// This is failing intermittently on CI and we don't have time to figure it out
|
||||
let fonts = FontSystem::new();
|
||||
let menlo = fonts.load_family("Menlo", &Default::default()).unwrap();
|
||||
let menlo_regular = RunStyle {
|
||||
font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
|
||||
color: Default::default(),
|
||||
underline: Default::default(),
|
||||
};
|
||||
let menlo_italic = RunStyle {
|
||||
font_id: fonts
|
||||
.select_font(&menlo, Properties::new().style(Style::Italic))
|
||||
.unwrap(),
|
||||
color: Default::default(),
|
||||
underline: Default::default(),
|
||||
};
|
||||
let menlo_bold = RunStyle {
|
||||
font_id: fonts
|
||||
.select_font(&menlo, Properties::new().weight(Weight::BOLD))
|
||||
.unwrap(),
|
||||
color: Default::default(),
|
||||
underline: Default::default(),
|
||||
};
|
||||
assert_ne!(menlo_regular, menlo_italic);
|
||||
assert_ne!(menlo_regular, menlo_bold);
|
||||
assert_ne!(menlo_italic, menlo_bold);
|
||||
|
||||
let line = fonts.layout_line(
|
||||
"hello world",
|
||||
16.0,
|
||||
&[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
|
||||
);
|
||||
assert_eq!(line.runs.len(), 3);
|
||||
assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
|
||||
assert_eq!(line.runs[0].glyphs.len(), 2);
|
||||
assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
|
||||
assert_eq!(line.runs[1].glyphs.len(), 4);
|
||||
assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
|
||||
assert_eq!(line.runs[2].glyphs.len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_glyph_offsets() -> anyhow::Result<()> {
|
||||
let fonts = FontSystem::new();
|
||||
let zapfino = fonts.load_family("Zapfino", &Default::default())?;
|
||||
let zapfino_regular = RunStyle {
|
||||
font_id: fonts.select_font(&zapfino, &Properties::new())?,
|
||||
color: Default::default(),
|
||||
underline: Default::default(),
|
||||
};
|
||||
let menlo = fonts.load_family("Menlo", &Default::default())?;
|
||||
let menlo_regular = RunStyle {
|
||||
font_id: fonts.select_font(&menlo, &Properties::new())?,
|
||||
color: Default::default(),
|
||||
underline: Default::default(),
|
||||
};
|
||||
|
||||
let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
|
||||
let line = fonts.layout_line(
|
||||
text,
|
||||
16.0,
|
||||
&[
|
||||
(9, zapfino_regular),
|
||||
(13, menlo_regular),
|
||||
(text.len() - 22, zapfino_regular),
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
line.runs
|
||||
.iter()
|
||||
.flat_map(|r| r.glyphs.iter())
|
||||
.map(|g| g.index)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37],
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_rasterize_glyph() {
|
||||
use std::{fs::File, io::BufWriter, path::Path};
|
||||
|
||||
let fonts = FontSystem::new();
|
||||
let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap();
|
||||
let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
|
||||
let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
|
||||
|
||||
const VARIANTS: usize = 1;
|
||||
for i in 0..VARIANTS {
|
||||
let variant = i as f32 / VARIANTS as f32;
|
||||
let (bounds, bytes) = fonts
|
||||
.rasterize_glyph(
|
||||
font_id,
|
||||
16.0,
|
||||
glyph_id,
|
||||
vec2f(variant, variant),
|
||||
2.,
|
||||
RasterizationOptions::Alpha,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
|
||||
let path = Path::new(&name);
|
||||
let file = File::create(path).unwrap();
|
||||
let w = &mut BufWriter::new(file);
|
||||
|
||||
let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
|
||||
encoder.set_color(png::ColorType::Grayscale);
|
||||
encoder.set_depth(png::BitDepth::Eight);
|
||||
let mut writer = encoder.write_header().unwrap();
|
||||
writer.write_image_data(&bytes).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrap_line() {
|
||||
let fonts = FontSystem::new();
|
||||
let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
|
||||
let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
|
||||
|
||||
let line = "one two three four five\n";
|
||||
let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
|
||||
assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]);
|
||||
|
||||
let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n";
|
||||
let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
|
||||
assert_eq!(
|
||||
wrap_boundaries,
|
||||
&["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_layout_line_bom_char() {
|
||||
let fonts = FontSystem::new();
|
||||
let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
|
||||
let style = RunStyle {
|
||||
font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
|
||||
color: Default::default(),
|
||||
underline: Default::default(),
|
||||
};
|
||||
|
||||
let line = "\u{feff}";
|
||||
let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
|
||||
assert_eq!(layout.len, line.len());
|
||||
assert!(layout.runs.is_empty());
|
||||
|
||||
let line = "a\u{feff}b";
|
||||
let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
|
||||
assert_eq!(layout.len, line.len());
|
||||
assert_eq!(layout.runs.len(), 1);
|
||||
assert_eq!(layout.runs[0].glyphs.len(), 2);
|
||||
assert_eq!(layout.runs[0].glyphs[0].id, 68); // a
|
||||
// There's no glyph for \u{feff}
|
||||
assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
use cocoa::{
|
||||
base::id,
|
||||
foundation::{NSPoint, NSRect},
|
||||
};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||
|
||||
///! Macos screen have a y axis that goings up from the bottom of the screen and
|
||||
///! an origin at the bottom left of the main display.
|
||||
|
||||
pub trait Vector2FExt {
|
||||
/// Converts self to an NSPoint with y axis pointing up.
|
||||
fn to_screen_ns_point(&self, native_window: id, window_height: f64) -> NSPoint;
|
||||
}
|
||||
|
||||
impl Vector2FExt for Vector2F {
|
||||
fn to_screen_ns_point(&self, native_window: id, window_height: f64) -> NSPoint {
|
||||
unsafe {
|
||||
let point = NSPoint::new(self.x() as f64, window_height - self.y() as f64);
|
||||
msg_send![native_window, convertPointToScreen: point]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NSRectExt {
|
||||
fn size_vec(&self) -> Vector2F;
|
||||
fn intersects(&self, other: Self) -> bool;
|
||||
}
|
||||
|
||||
impl NSRectExt for NSRect {
|
||||
fn size_vec(&self) -> Vector2F {
|
||||
vec2f(self.size.width as f32, self.size.height as f32)
|
||||
}
|
||||
|
||||
fn intersects(&self, other: Self) -> bool {
|
||||
self.size.width > 0.
|
||||
&& self.size.height > 0.
|
||||
&& other.size.width > 0.
|
||||
&& other.size.height > 0.
|
||||
&& self.origin.x <= other.origin.x + other.size.width
|
||||
&& self.origin.x + self.size.width >= other.origin.x
|
||||
&& self.origin.y <= other.origin.y + other.size.height
|
||||
&& self.origin.y + self.size.height >= other.origin.y
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
use super::atlas::{AllocId, AtlasAllocator};
|
||||
use crate::{
|
||||
fonts::{FontId, GlyphId},
|
||||
geometry::{rect::RectI, vector::Vector2I},
|
||||
platform::{FontSystem, RasterizationOptions},
|
||||
scene::ImageGlyph,
|
||||
ImageData,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use metal::{MTLPixelFormat, TextureDescriptor, TextureRef};
|
||||
use ordered_float::OrderedFloat;
|
||||
use std::{collections::HashMap, mem, sync::Arc};
|
||||
|
||||
#[derive(Hash, Eq, PartialEq)]
|
||||
struct GlyphDescriptor {
|
||||
font_id: FontId,
|
||||
font_size: OrderedFloat<f32>,
|
||||
glyph_id: GlyphId,
|
||||
}
|
||||
|
||||
pub struct ImageCache {
|
||||
prev_frame: HashMap<usize, (AllocId, RectI)>,
|
||||
curr_frame: HashMap<usize, (AllocId, RectI)>,
|
||||
image_glyphs: HashMap<GlyphDescriptor, Option<(AllocId, RectI, Vector2I)>>,
|
||||
atlases: AtlasAllocator,
|
||||
scale_factor: f32,
|
||||
fonts: Arc<dyn FontSystem>,
|
||||
}
|
||||
|
||||
impl ImageCache {
|
||||
pub fn new(
|
||||
device: metal::Device,
|
||||
size: Vector2I,
|
||||
scale_factor: f32,
|
||||
fonts: Arc<dyn FontSystem>,
|
||||
) -> Self {
|
||||
let descriptor = TextureDescriptor::new();
|
||||
descriptor.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
|
||||
descriptor.set_width(size.x() as u64);
|
||||
descriptor.set_height(size.y() as u64);
|
||||
Self {
|
||||
prev_frame: Default::default(),
|
||||
curr_frame: Default::default(),
|
||||
image_glyphs: Default::default(),
|
||||
atlases: AtlasAllocator::new(device, descriptor),
|
||||
scale_factor,
|
||||
fonts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_scale_factor(&mut self, scale_factor: f32) {
|
||||
if scale_factor != self.scale_factor {
|
||||
self.scale_factor = scale_factor;
|
||||
for (_, glyph) in self.image_glyphs.drain() {
|
||||
if let Some((alloc_id, _, _)) = glyph {
|
||||
self.atlases.deallocate(alloc_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&mut self, image: &ImageData) -> (AllocId, RectI) {
|
||||
let (alloc_id, atlas_bounds) = self
|
||||
.prev_frame
|
||||
.remove(&image.id)
|
||||
.or_else(|| self.curr_frame.get(&image.id).copied())
|
||||
.or_else(|| self.atlases.upload(image.size(), image.as_bytes()))
|
||||
.ok_or_else(|| anyhow!("could not upload image of size {:?}", image.size()))
|
||||
.unwrap();
|
||||
self.curr_frame.insert(image.id, (alloc_id, atlas_bounds));
|
||||
(alloc_id, atlas_bounds)
|
||||
}
|
||||
|
||||
pub fn render_glyph(&mut self, image_glyph: &ImageGlyph) -> Option<(AllocId, RectI, Vector2I)> {
|
||||
*self
|
||||
.image_glyphs
|
||||
.entry(GlyphDescriptor {
|
||||
font_id: image_glyph.font_id,
|
||||
font_size: OrderedFloat(image_glyph.font_size),
|
||||
glyph_id: image_glyph.id,
|
||||
})
|
||||
.or_insert_with(|| {
|
||||
let (glyph_bounds, bytes) = self.fonts.rasterize_glyph(
|
||||
image_glyph.font_id,
|
||||
image_glyph.font_size,
|
||||
image_glyph.id,
|
||||
Default::default(),
|
||||
self.scale_factor,
|
||||
RasterizationOptions::Bgra,
|
||||
)?;
|
||||
let (alloc_id, atlas_bounds) = self
|
||||
.atlases
|
||||
.upload(glyph_bounds.size(), &bytes)
|
||||
.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"could not upload image glyph of size {:?}",
|
||||
glyph_bounds.size()
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
Some((alloc_id, atlas_bounds, glyph_bounds.origin()))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn finish_frame(&mut self) {
|
||||
mem::swap(&mut self.prev_frame, &mut self.curr_frame);
|
||||
for (_, (id, _)) in self.curr_frame.drain() {
|
||||
self.atlases.deallocate(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn atlas_texture(&self, atlas_id: usize) -> Option<&TextureRef> {
|
||||
self.atlases.texture(atlas_id)
|
||||
}
|
||||
}
|
256
crates/gpui/src/platform/mac/metal_atlas.rs
Normal file
256
crates/gpui/src/platform/mac/metal_atlas.rs
Normal file
|
@ -0,0 +1,256 @@
|
|||
use crate::{
|
||||
AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
|
||||
Point, Size,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use collections::FxHashMap;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use etagere::BucketedAtlasAllocator;
|
||||
use metal::Device;
|
||||
use parking_lot::Mutex;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub struct MetalAtlas(Mutex<MetalAtlasState>);
|
||||
|
||||
impl MetalAtlas {
|
||||
pub fn new(device: Device) -> Self {
|
||||
MetalAtlas(Mutex::new(MetalAtlasState {
|
||||
device: AssertSend(device),
|
||||
monochrome_textures: Default::default(),
|
||||
polychrome_textures: Default::default(),
|
||||
path_textures: Default::default(),
|
||||
tiles_by_key: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn metal_texture(&self, id: AtlasTextureId) -> metal::Texture {
|
||||
self.0.lock().texture(id).metal_texture.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn allocate(
|
||||
&self,
|
||||
size: Size<DevicePixels>,
|
||||
texture_kind: AtlasTextureKind,
|
||||
) -> AtlasTile {
|
||||
self.0.lock().allocate(size, texture_kind)
|
||||
}
|
||||
|
||||
pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
|
||||
let mut lock = self.0.lock();
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut lock.path_textures,
|
||||
};
|
||||
for texture in textures {
|
||||
texture.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MetalAtlasState {
|
||||
device: AssertSend<Device>,
|
||||
monochrome_textures: Vec<MetalAtlasTexture>,
|
||||
polychrome_textures: Vec<MetalAtlasTexture>,
|
||||
path_textures: Vec<MetalAtlasTexture>,
|
||||
tiles_by_key: FxHashMap<AtlasKey, AtlasTile>,
|
||||
}
|
||||
|
||||
impl PlatformAtlas for MetalAtlas {
|
||||
fn get_or_insert_with<'a>(
|
||||
&self,
|
||||
key: &AtlasKey,
|
||||
build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
|
||||
) -> Result<AtlasTile> {
|
||||
let mut lock = self.0.lock();
|
||||
if let Some(tile) = lock.tiles_by_key.get(key) {
|
||||
Ok(tile.clone())
|
||||
} else {
|
||||
let (size, bytes) = build()?;
|
||||
let tile = lock.allocate(size, key.texture_kind());
|
||||
let texture = lock.texture(tile.texture_id);
|
||||
texture.upload(tile.bounds, &bytes);
|
||||
lock.tiles_by_key.insert(key.clone(), tile.clone());
|
||||
Ok(tile)
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&self) {
|
||||
let mut lock = self.0.lock();
|
||||
lock.tiles_by_key.clear();
|
||||
for texture in &mut lock.monochrome_textures {
|
||||
texture.clear();
|
||||
}
|
||||
for texture in &mut lock.polychrome_textures {
|
||||
texture.clear();
|
||||
}
|
||||
for texture in &mut lock.path_textures {
|
||||
texture.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MetalAtlasState {
|
||||
fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
|
||||
let textures = match texture_kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut self.path_textures,
|
||||
};
|
||||
textures
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|texture| texture.allocate(size))
|
||||
.unwrap_or_else(|| {
|
||||
let texture = self.push_texture(size, texture_kind);
|
||||
texture.allocate(size).unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
fn push_texture(
|
||||
&mut self,
|
||||
min_size: Size<DevicePixels>,
|
||||
kind: AtlasTextureKind,
|
||||
) -> &mut MetalAtlasTexture {
|
||||
const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
|
||||
width: DevicePixels(1024),
|
||||
height: DevicePixels(1024),
|
||||
};
|
||||
|
||||
let size = min_size.max(&DEFAULT_ATLAS_SIZE);
|
||||
let texture_descriptor = metal::TextureDescriptor::new();
|
||||
texture_descriptor.set_width(size.width.into());
|
||||
texture_descriptor.set_height(size.height.into());
|
||||
let pixel_format;
|
||||
let usage;
|
||||
match kind {
|
||||
AtlasTextureKind::Monochrome => {
|
||||
pixel_format = metal::MTLPixelFormat::A8Unorm;
|
||||
usage = metal::MTLTextureUsage::ShaderRead;
|
||||
}
|
||||
AtlasTextureKind::Polychrome => {
|
||||
pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
|
||||
usage = metal::MTLTextureUsage::ShaderRead;
|
||||
}
|
||||
AtlasTextureKind::Path => {
|
||||
pixel_format = metal::MTLPixelFormat::R16Float;
|
||||
usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;
|
||||
}
|
||||
}
|
||||
texture_descriptor.set_pixel_format(pixel_format);
|
||||
texture_descriptor.set_usage(usage);
|
||||
let metal_texture = self.device.new_texture(&texture_descriptor);
|
||||
|
||||
let textures = match kind {
|
||||
AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
|
||||
AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
|
||||
AtlasTextureKind::Path => &mut self.path_textures,
|
||||
};
|
||||
let atlas_texture = MetalAtlasTexture {
|
||||
id: AtlasTextureId {
|
||||
index: textures.len() as u32,
|
||||
kind,
|
||||
},
|
||||
allocator: etagere::BucketedAtlasAllocator::new(size.into()),
|
||||
metal_texture: AssertSend(metal_texture),
|
||||
};
|
||||
textures.push(atlas_texture);
|
||||
textures.last_mut().unwrap()
|
||||
}
|
||||
|
||||
fn texture(&self, id: AtlasTextureId) -> &MetalAtlasTexture {
|
||||
let textures = match id.kind {
|
||||
crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
|
||||
crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
|
||||
crate::AtlasTextureKind::Path => &self.path_textures,
|
||||
};
|
||||
&textures[id.index as usize]
|
||||
}
|
||||
}
|
||||
|
||||
struct MetalAtlasTexture {
|
||||
id: AtlasTextureId,
|
||||
allocator: BucketedAtlasAllocator,
|
||||
metal_texture: AssertSend<metal::Texture>,
|
||||
}
|
||||
|
||||
impl MetalAtlasTexture {
|
||||
fn clear(&mut self) {
|
||||
self.allocator.clear();
|
||||
}
|
||||
|
||||
fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
|
||||
let allocation = self.allocator.allocate(size.into())?;
|
||||
let tile = AtlasTile {
|
||||
texture_id: self.id,
|
||||
tile_id: allocation.id.into(),
|
||||
bounds: Bounds {
|
||||
origin: allocation.rectangle.min.into(),
|
||||
size,
|
||||
},
|
||||
};
|
||||
Some(tile)
|
||||
}
|
||||
|
||||
fn upload(&self, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
|
||||
let region = metal::MTLRegion::new_2d(
|
||||
bounds.origin.x.into(),
|
||||
bounds.origin.y.into(),
|
||||
bounds.size.width.into(),
|
||||
bounds.size.height.into(),
|
||||
);
|
||||
self.metal_texture.replace_region(
|
||||
region,
|
||||
0,
|
||||
bytes.as_ptr() as *const _,
|
||||
bounds.size.width.to_bytes(self.bytes_per_pixel()) as u64,
|
||||
);
|
||||
}
|
||||
|
||||
fn bytes_per_pixel(&self) -> u8 {
|
||||
use metal::MTLPixelFormat::*;
|
||||
match self.metal_texture.pixel_format() {
|
||||
A8Unorm | R8Unorm => 1,
|
||||
RGBA8Unorm | BGRA8Unorm => 4,
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Size<DevicePixels>> for etagere::Size {
|
||||
fn from(size: Size<DevicePixels>) -> Self {
|
||||
etagere::Size::new(size.width.into(), size.height.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<etagere::Point> for Point<DevicePixels> {
|
||||
fn from(value: etagere::Point) -> Self {
|
||||
Point {
|
||||
x: DevicePixels::from(value.x),
|
||||
y: DevicePixels::from(value.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<etagere::Size> for Size<DevicePixels> {
|
||||
fn from(size: etagere::Size) -> Self {
|
||||
Size {
|
||||
width: DevicePixels::from(size.width),
|
||||
height: DevicePixels::from(size.height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<etagere::Rectangle> for Bounds<DevicePixels> {
|
||||
fn from(rectangle: etagere::Rectangle) -> Self {
|
||||
Bounds {
|
||||
origin: rectangle.min.into(),
|
||||
size: rectangle.size().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
struct AssertSend<T>(T);
|
||||
|
||||
unsafe impl<T> Send for AssertSend<T> {}
|
1051
crates/gpui/src/platform/mac/metal_renderer.rs
Normal file
1051
crates/gpui/src/platform/mac/metal_renderer.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,8 +1,6 @@
|
|||
#![allow(unused, non_upper_case_globals)]
|
||||
|
||||
use std::ptr;
|
||||
|
||||
use crate::fonts::Features;
|
||||
use crate::FontFeatures;
|
||||
use cocoa::appkit::CGFloat;
|
||||
use core_foundation::{base::TCFType, number::CFNumber};
|
||||
use core_graphics::geometry::CGAffineTransform;
|
||||
|
@ -13,6 +11,7 @@ use core_text::{
|
|||
},
|
||||
};
|
||||
use font_kit::font::Font;
|
||||
use std::ptr;
|
||||
|
||||
const kCaseSensitiveLayoutOffSelector: i32 = 1;
|
||||
const kCaseSensitiveLayoutOnSelector: i32 = 0;
|
||||
|
@ -108,243 +107,243 @@ const kTypographicExtrasType: i32 = 14;
|
|||
const kVerticalFractionsSelector: i32 = 1;
|
||||
const kVerticalPositionType: i32 = 10;
|
||||
|
||||
pub fn apply_features(font: &mut Font, features: &Features) {
|
||||
pub fn apply_features(font: &mut Font, features: FontFeatures) {
|
||||
// See https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/third_party/harfbuzz-ng/src/hb-coretext.cc
|
||||
// for a reference implementation.
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.calt,
|
||||
features.calt(),
|
||||
kContextualAlternatesType,
|
||||
kContextualAlternatesOnSelector,
|
||||
kContextualAlternatesOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.case,
|
||||
features.case(),
|
||||
kCaseSensitiveLayoutType,
|
||||
kCaseSensitiveLayoutOnSelector,
|
||||
kCaseSensitiveLayoutOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.cpsp,
|
||||
features.cpsp(),
|
||||
kCaseSensitiveLayoutType,
|
||||
kCaseSensitiveSpacingOnSelector,
|
||||
kCaseSensitiveSpacingOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.frac,
|
||||
features.frac(),
|
||||
kFractionsType,
|
||||
kDiagonalFractionsSelector,
|
||||
kNoFractionsSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.liga,
|
||||
features.liga(),
|
||||
kLigaturesType,
|
||||
kCommonLigaturesOnSelector,
|
||||
kCommonLigaturesOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.onum,
|
||||
features.onum(),
|
||||
kNumberCaseType,
|
||||
kLowerCaseNumbersSelector,
|
||||
2,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ordn,
|
||||
features.ordn(),
|
||||
kVerticalPositionType,
|
||||
kOrdinalsSelector,
|
||||
kNormalPositionSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.pnum,
|
||||
features.pnum(),
|
||||
kNumberSpacingType,
|
||||
kProportionalNumbersSelector,
|
||||
4,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss01,
|
||||
features.ss01(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltOneOnSelector,
|
||||
kStylisticAltOneOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss02,
|
||||
features.ss02(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltTwoOnSelector,
|
||||
kStylisticAltTwoOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss03,
|
||||
features.ss03(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltThreeOnSelector,
|
||||
kStylisticAltThreeOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss04,
|
||||
features.ss04(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltFourOnSelector,
|
||||
kStylisticAltFourOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss05,
|
||||
features.ss05(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltFiveOnSelector,
|
||||
kStylisticAltFiveOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss06,
|
||||
features.ss06(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltSixOnSelector,
|
||||
kStylisticAltSixOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss07,
|
||||
features.ss07(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltSevenOnSelector,
|
||||
kStylisticAltSevenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss08,
|
||||
features.ss08(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltEightOnSelector,
|
||||
kStylisticAltEightOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss09,
|
||||
features.ss09(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltNineOnSelector,
|
||||
kStylisticAltNineOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss10,
|
||||
features.ss10(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltTenOnSelector,
|
||||
kStylisticAltTenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss11,
|
||||
features.ss11(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltElevenOnSelector,
|
||||
kStylisticAltElevenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss12,
|
||||
features.ss12(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltTwelveOnSelector,
|
||||
kStylisticAltTwelveOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss13,
|
||||
features.ss13(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltThirteenOnSelector,
|
||||
kStylisticAltThirteenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss14,
|
||||
features.ss14(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltFourteenOnSelector,
|
||||
kStylisticAltFourteenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss15,
|
||||
features.ss15(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltFifteenOnSelector,
|
||||
kStylisticAltFifteenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss16,
|
||||
features.ss16(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltSixteenOnSelector,
|
||||
kStylisticAltSixteenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss17,
|
||||
features.ss17(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltSeventeenOnSelector,
|
||||
kStylisticAltSeventeenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss18,
|
||||
features.ss18(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltEighteenOnSelector,
|
||||
kStylisticAltEighteenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss19,
|
||||
features.ss19(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltNineteenOnSelector,
|
||||
kStylisticAltNineteenOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.ss20,
|
||||
features.ss20(),
|
||||
kStylisticAlternativesType,
|
||||
kStylisticAltTwentyOnSelector,
|
||||
kStylisticAltTwentyOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.subs,
|
||||
features.subs(),
|
||||
kVerticalPositionType,
|
||||
kInferiorsSelector,
|
||||
kNormalPositionSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.sups,
|
||||
features.sups(),
|
||||
kVerticalPositionType,
|
||||
kSuperiorsSelector,
|
||||
kNormalPositionSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.swsh,
|
||||
features.swsh(),
|
||||
kContextualAlternatesType,
|
||||
kSwashAlternatesOnSelector,
|
||||
kSwashAlternatesOffSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.titl,
|
||||
features.titl(),
|
||||
kStyleOptionsType,
|
||||
kTitlingCapsSelector,
|
||||
kNoStyleOptionsSelector,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.tnum,
|
||||
features.tnum(),
|
||||
kNumberSpacingType,
|
||||
kMonospacedNumbersSelector,
|
||||
4,
|
||||
);
|
||||
toggle_open_type_feature(
|
||||
font,
|
||||
features.zero,
|
||||
features.zero(),
|
||||
kTypographicExtrasType,
|
||||
kSlashedZeroOnSelector,
|
||||
kSlashedZeroOffSelector,
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,144 +0,0 @@
|
|||
use super::ns_string;
|
||||
use crate::platform;
|
||||
use cocoa::{
|
||||
appkit::NSScreen,
|
||||
base::{id, nil},
|
||||
foundation::{NSArray, NSDictionary, NSPoint, NSRect, NSSize},
|
||||
};
|
||||
use core_foundation::{
|
||||
number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef},
|
||||
uuid::{CFUUIDGetUUIDBytes, CFUUIDRef},
|
||||
};
|
||||
use core_graphics::display::CGDirectDisplayID;
|
||||
use pathfinder_geometry::{rect::RectF, vector::vec2f};
|
||||
use std::{any::Any, ffi::c_void};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Screen {
|
||||
pub(crate) native_screen: id,
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
/// Get the screen with the given UUID.
|
||||
pub fn find_by_id(uuid: Uuid) -> Option<Self> {
|
||||
Self::all().find(|screen| platform::Screen::display_uuid(screen) == Some(uuid))
|
||||
}
|
||||
|
||||
/// Get the primary screen - the one with the menu bar, and whose bottom left
|
||||
/// corner is at the origin of the AppKit coordinate system.
|
||||
fn primary() -> Self {
|
||||
Self::all().next().unwrap()
|
||||
}
|
||||
|
||||
pub fn all() -> impl Iterator<Item = Self> {
|
||||
unsafe {
|
||||
let native_screens = NSScreen::screens(nil);
|
||||
(0..NSArray::count(native_screens)).map(move |ix| Screen {
|
||||
native_screen: native_screens.objectAtIndex(ix),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the given rectangle in screen coordinates from GPUI's
|
||||
/// coordinate system to the AppKit coordinate system.
|
||||
///
|
||||
/// In GPUI's coordinates, the origin is at the top left of the primary screen, with
|
||||
/// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
|
||||
/// bottom left of the primary screen, with the Y axis pointing upward.
|
||||
pub(crate) fn screen_rect_to_native(rect: RectF) -> NSRect {
|
||||
let primary_screen_height = unsafe { Self::primary().native_screen.frame().size.height };
|
||||
NSRect::new(
|
||||
NSPoint::new(
|
||||
rect.origin_x() as f64,
|
||||
primary_screen_height - rect.origin_y() as f64 - rect.height() as f64,
|
||||
),
|
||||
NSSize::new(rect.width() as f64, rect.height() as f64),
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert the given rectangle in screen coordinates from the AppKit
|
||||
/// coordinate system to GPUI's coordinate system.
|
||||
///
|
||||
/// In GPUI's coordinates, the origin is at the top left of the primary screen, with
|
||||
/// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
|
||||
/// bottom left of the primary screen, with the Y axis pointing upward.
|
||||
pub(crate) fn screen_rect_from_native(rect: NSRect) -> RectF {
|
||||
let primary_screen_height = unsafe { Self::primary().native_screen.frame().size.height };
|
||||
RectF::new(
|
||||
vec2f(
|
||||
rect.origin.x as f32,
|
||||
(primary_screen_height - rect.origin.y - rect.size.height) as f32,
|
||||
),
|
||||
vec2f(rect.size.width as f32, rect.size.height as f32),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl platform::Screen for Screen {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn display_uuid(&self) -> Option<uuid::Uuid> {
|
||||
unsafe {
|
||||
// Screen ids are not stable. Further, the default device id is also unstable across restarts.
|
||||
// CGDisplayCreateUUIDFromDisplayID is stable but not exposed in the bindings we use.
|
||||
// This approach is similar to that which winit takes
|
||||
// https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99
|
||||
let device_description = self.native_screen.deviceDescription();
|
||||
|
||||
let key = ns_string("NSScreenNumber");
|
||||
let device_id_obj = device_description.objectForKey_(key);
|
||||
if device_id_obj.is_null() {
|
||||
// Under some circumstances, especially display re-arrangements or display locking, we seem to get a null pointer
|
||||
// to the device id. See: https://linear.app/zed-industries/issue/Z-257/lock-screen-crash-with-multiple-monitors
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut device_id: u32 = 0;
|
||||
CFNumberGetValue(
|
||||
device_id_obj as CFNumberRef,
|
||||
kCFNumberIntType,
|
||||
(&mut device_id) as *mut _ as *mut c_void,
|
||||
);
|
||||
let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID);
|
||||
if cfuuid.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let bytes = CFUUIDGetUUIDBytes(cfuuid);
|
||||
Some(Uuid::from_bytes([
|
||||
bytes.byte0,
|
||||
bytes.byte1,
|
||||
bytes.byte2,
|
||||
bytes.byte3,
|
||||
bytes.byte4,
|
||||
bytes.byte5,
|
||||
bytes.byte6,
|
||||
bytes.byte7,
|
||||
bytes.byte8,
|
||||
bytes.byte9,
|
||||
bytes.byte10,
|
||||
bytes.byte11,
|
||||
bytes.byte12,
|
||||
bytes.byte13,
|
||||
bytes.byte14,
|
||||
bytes.byte15,
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
fn bounds(&self) -> RectF {
|
||||
unsafe { Self::screen_rect_from_native(self.native_screen.frame()) }
|
||||
}
|
||||
|
||||
fn content_bounds(&self) -> RectF {
|
||||
unsafe { Self::screen_rect_from_native(self.native_screen.visibleFrame()) }
|
||||
}
|
||||
}
|
655
crates/gpui/src/platform/mac/shaders.metal
Normal file
655
crates/gpui/src/platform/mac/shaders.metal
Normal file
|
@ -0,0 +1,655 @@
|
|||
#include <metal_stdlib>
|
||||
#include <simd/simd.h>
|
||||
|
||||
using namespace metal;
|
||||
|
||||
float4 hsla_to_rgba(Hsla hsla);
|
||||
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
constant Size_DevicePixels *viewport_size);
|
||||
float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
|
||||
constant Size_DevicePixels *atlas_size);
|
||||
float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds);
|
||||
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
|
||||
Corners_ScaledPixels corner_radii);
|
||||
float gaussian(float x, float sigma);
|
||||
float2 erf(float2 x);
|
||||
float blur_along_x(float x, float y, float sigma, float corner,
|
||||
float2 half_size);
|
||||
|
||||
struct QuadVertexOutput {
|
||||
float4 position [[position]];
|
||||
float4 background_color [[flat]];
|
||||
float4 border_color [[flat]];
|
||||
uint quad_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct QuadFragmentInput {
|
||||
float4 position [[position]];
|
||||
float4 background_color [[flat]];
|
||||
float4 border_color [[flat]];
|
||||
uint quad_id [[flat]];
|
||||
};
|
||||
|
||||
vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
|
||||
uint quad_id [[instance_id]],
|
||||
constant float2 *unit_vertices
|
||||
[[buffer(QuadInputIndex_Vertices)]],
|
||||
constant Quad *quads
|
||||
[[buffer(QuadInputIndex_Quads)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(QuadInputIndex_ViewportSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
Quad quad = quads[quad_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, quad.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds,
|
||||
quad.content_mask.bounds);
|
||||
float4 background_color = hsla_to_rgba(quad.background);
|
||||
float4 border_color = hsla_to_rgba(quad.border_color);
|
||||
return QuadVertexOutput{
|
||||
device_position,
|
||||
background_color,
|
||||
border_color,
|
||||
quad_id,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
|
||||
constant Quad *quads
|
||||
[[buffer(QuadInputIndex_Quads)]]) {
|
||||
Quad quad = quads[input.quad_id];
|
||||
float2 half_size =
|
||||
float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
|
||||
float2 center =
|
||||
float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
|
||||
float2 center_to_point = input.position.xy - center;
|
||||
float corner_radius;
|
||||
if (center_to_point.x < 0.) {
|
||||
if (center_to_point.y < 0.) {
|
||||
corner_radius = quad.corner_radii.top_left;
|
||||
} else {
|
||||
corner_radius = quad.corner_radii.bottom_left;
|
||||
}
|
||||
} else {
|
||||
if (center_to_point.y < 0.) {
|
||||
corner_radius = quad.corner_radii.top_right;
|
||||
} else {
|
||||
corner_radius = quad.corner_radii.bottom_right;
|
||||
}
|
||||
}
|
||||
|
||||
float2 rounded_edge_to_point =
|
||||
fabs(center_to_point) - half_size + corner_radius;
|
||||
float distance =
|
||||
length(max(0., rounded_edge_to_point)) +
|
||||
min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
|
||||
corner_radius;
|
||||
|
||||
float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left
|
||||
: quad.border_widths.right;
|
||||
float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top
|
||||
: quad.border_widths.bottom;
|
||||
float2 inset_size =
|
||||
half_size - corner_radius - float2(vertical_border, horizontal_border);
|
||||
float2 point_to_inset_corner = fabs(center_to_point) - inset_size;
|
||||
float border_width;
|
||||
if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
|
||||
border_width = 0.;
|
||||
} else if (point_to_inset_corner.y > point_to_inset_corner.x) {
|
||||
border_width = horizontal_border;
|
||||
} else {
|
||||
border_width = vertical_border;
|
||||
}
|
||||
|
||||
float4 color;
|
||||
if (border_width == 0.) {
|
||||
color = input.background_color;
|
||||
} else {
|
||||
float inset_distance = distance + border_width;
|
||||
|
||||
// Decrease border's opacity as we move inside the background.
|
||||
input.border_color.a *= 1. - saturate(0.5 - inset_distance);
|
||||
|
||||
// Alpha-blend the border and the background.
|
||||
float output_alpha = input.border_color.a +
|
||||
input.background_color.a * (1. - input.border_color.a);
|
||||
float3 premultiplied_border_rgb =
|
||||
input.border_color.rgb * input.border_color.a;
|
||||
float3 premultiplied_background_rgb =
|
||||
input.background_color.rgb * input.background_color.a;
|
||||
float3 premultiplied_output_rgb =
|
||||
premultiplied_border_rgb +
|
||||
premultiplied_background_rgb * (1. - input.border_color.a);
|
||||
color = float4(premultiplied_output_rgb, output_alpha);
|
||||
}
|
||||
|
||||
return color * float4(1., 1., 1., saturate(0.5 - distance));
|
||||
}
|
||||
|
||||
struct ShadowVertexOutput {
|
||||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint shadow_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct ShadowFragmentInput {
|
||||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint shadow_id [[flat]];
|
||||
};
|
||||
|
||||
vertex ShadowVertexOutput shadow_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint shadow_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(ShadowInputIndex_Vertices)]],
|
||||
constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(ShadowInputIndex_ViewportSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
Shadow shadow = shadows[shadow_id];
|
||||
|
||||
float margin = 3. * shadow.blur_radius;
|
||||
// Set the bounds of the shadow and adjust its size based on the shadow's
|
||||
// spread radius to achieve the spreading effect
|
||||
Bounds_ScaledPixels bounds = shadow.bounds;
|
||||
bounds.origin.x -= margin;
|
||||
bounds.origin.y -= margin;
|
||||
bounds.size.width += 2. * margin;
|
||||
bounds.size.height += 2. * margin;
|
||||
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, bounds, viewport_size);
|
||||
float4 clip_distance =
|
||||
distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask.bounds);
|
||||
float4 color = hsla_to_rgba(shadow.color);
|
||||
|
||||
return ShadowVertexOutput{
|
||||
device_position,
|
||||
color,
|
||||
shadow_id,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]],
|
||||
constant Shadow *shadows
|
||||
[[buffer(ShadowInputIndex_Shadows)]]) {
|
||||
Shadow shadow = shadows[input.shadow_id];
|
||||
|
||||
float2 origin = float2(shadow.bounds.origin.x, shadow.bounds.origin.y);
|
||||
float2 size = float2(shadow.bounds.size.width, shadow.bounds.size.height);
|
||||
float2 half_size = size / 2.;
|
||||
float2 center = origin + half_size;
|
||||
float2 point = input.position.xy - center;
|
||||
float corner_radius;
|
||||
if (point.x < 0.) {
|
||||
if (point.y < 0.) {
|
||||
corner_radius = shadow.corner_radii.top_left;
|
||||
} else {
|
||||
corner_radius = shadow.corner_radii.bottom_left;
|
||||
}
|
||||
} else {
|
||||
if (point.y < 0.) {
|
||||
corner_radius = shadow.corner_radii.top_right;
|
||||
} else {
|
||||
corner_radius = shadow.corner_radii.bottom_right;
|
||||
}
|
||||
}
|
||||
|
||||
// The signal is only non-zero in a limited range, so don't waste samples
|
||||
float low = point.y - half_size.y;
|
||||
float high = point.y + half_size.y;
|
||||
float start = clamp(-3. * shadow.blur_radius, low, high);
|
||||
float end = clamp(3. * shadow.blur_radius, low, high);
|
||||
|
||||
// Accumulate samples (we can get away with surprisingly few samples)
|
||||
float step = (end - start) / 4.;
|
||||
float y = start + step * 0.5;
|
||||
float alpha = 0.;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius,
|
||||
corner_radius, half_size) *
|
||||
gaussian(y, shadow.blur_radius) * step;
|
||||
y += step;
|
||||
}
|
||||
|
||||
return input.color * float4(1., 1., 1., alpha);
|
||||
}
|
||||
|
||||
struct UnderlineVertexOutput {
|
||||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint underline_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct UnderlineFragmentInput {
|
||||
float4 position [[position]];
|
||||
float4 color [[flat]];
|
||||
uint underline_id [[flat]];
|
||||
};
|
||||
|
||||
vertex UnderlineVertexOutput underline_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint underline_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(UnderlineInputIndex_Vertices)]],
|
||||
constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(ShadowInputIndex_ViewportSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
Underline underline = underlines[underline_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, underline.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
|
||||
underline.content_mask.bounds);
|
||||
float4 color = hsla_to_rgba(underline.color);
|
||||
return UnderlineVertexOutput{
|
||||
device_position,
|
||||
color,
|
||||
underline_id,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 underline_fragment(UnderlineFragmentInput input [[stage_in]],
|
||||
constant Underline *underlines
|
||||
[[buffer(UnderlineInputIndex_Underlines)]]) {
|
||||
Underline underline = underlines[input.underline_id];
|
||||
if (underline.wavy) {
|
||||
float half_thickness = underline.thickness * 0.5;
|
||||
float2 origin =
|
||||
float2(underline.bounds.origin.x, underline.bounds.origin.y);
|
||||
float2 st = ((input.position.xy - origin) / underline.bounds.size.height) -
|
||||
float2(0., 0.5);
|
||||
float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
|
||||
float amplitude = 1. / (2. * underline.thickness);
|
||||
float sine = sin(st.x * frequency) * amplitude;
|
||||
float dSine = cos(st.x * frequency) * amplitude * frequency;
|
||||
float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
|
||||
float distance_in_pixels = distance * underline.bounds.size.height;
|
||||
float distance_from_top_border = distance_in_pixels - half_thickness;
|
||||
float distance_from_bottom_border = distance_in_pixels + half_thickness;
|
||||
float alpha = saturate(
|
||||
0.5 - max(-distance_from_bottom_border, distance_from_top_border));
|
||||
return input.color * float4(1., 1., 1., alpha);
|
||||
} else {
|
||||
return input.color;
|
||||
}
|
||||
}
|
||||
|
||||
struct MonochromeSpriteVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
float4 color [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct MonochromeSpriteFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
float4 color [[flat]];
|
||||
};
|
||||
|
||||
vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
|
||||
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(SpriteInputIndex_ViewportSize)]],
|
||||
constant Size_DevicePixels *atlas_size
|
||||
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
MonochromeSprite sprite = sprites[sprite_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, sprite.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
|
||||
sprite.content_mask.bounds);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
float4 color = hsla_to_rgba(sprite.color);
|
||||
return MonochromeSpriteVertexOutput{
|
||||
device_position,
|
||||
tile_position,
|
||||
color,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 monochrome_sprite_fragment(
|
||||
MonochromeSpriteFragmentInput input [[stage_in]],
|
||||
constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||
min_filter::linear);
|
||||
float4 sample =
|
||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||
float4 color = input.color;
|
||||
color.a *= sample.a;
|
||||
return color;
|
||||
}
|
||||
|
||||
struct PolychromeSpriteVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
uint sprite_id [[flat]];
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct PolychromeSpriteFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
uint sprite_id [[flat]];
|
||||
};
|
||||
|
||||
vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
|
||||
constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(SpriteInputIndex_ViewportSize)]],
|
||||
constant Size_DevicePixels *atlas_size
|
||||
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
||||
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
PolychromeSprite sprite = sprites[sprite_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, sprite.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
|
||||
sprite.content_mask.bounds);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
return PolychromeSpriteVertexOutput{
|
||||
device_position,
|
||||
tile_position,
|
||||
sprite_id,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 polychrome_sprite_fragment(
|
||||
PolychromeSpriteFragmentInput input [[stage_in]],
|
||||
constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||
PolychromeSprite sprite = sprites[input.sprite_id];
|
||||
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||
min_filter::linear);
|
||||
float4 sample =
|
||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||
float distance =
|
||||
quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
|
||||
|
||||
float4 color = sample;
|
||||
if (sprite.grayscale) {
|
||||
float grayscale = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
|
||||
color.r = grayscale;
|
||||
color.g = grayscale;
|
||||
color.b = grayscale;
|
||||
}
|
||||
color.a *= saturate(0.5 - distance);
|
||||
return color;
|
||||
}
|
||||
|
||||
struct PathRasterizationVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 st_position;
|
||||
float clip_rect_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct PathRasterizationFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 st_position;
|
||||
};
|
||||
|
||||
vertex PathRasterizationVertexOutput path_rasterization_vertex(
|
||||
uint vertex_id [[vertex_id]],
|
||||
constant PathVertex_ScaledPixels *vertices
|
||||
[[buffer(PathRasterizationInputIndex_Vertices)]],
|
||||
constant Size_DevicePixels *atlas_size
|
||||
[[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
|
||||
PathVertex_ScaledPixels v = vertices[vertex_id];
|
||||
float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
|
||||
float2 viewport_size = float2(atlas_size->width, atlas_size->height);
|
||||
return PathRasterizationVertexOutput{
|
||||
float4(vertex_position / viewport_size * float2(2., -2.) +
|
||||
float2(-1., 1.),
|
||||
0., 1.),
|
||||
float2(v.st_position.x, v.st_position.y),
|
||||
{v.xy_position.x - v.content_mask.bounds.origin.x,
|
||||
v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
|
||||
v.xy_position.x,
|
||||
v.xy_position.y - v.content_mask.bounds.origin.y,
|
||||
v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
|
||||
v.xy_position.y}};
|
||||
}
|
||||
|
||||
fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
|
||||
[[stage_in]]) {
|
||||
float2 dx = dfdx(input.st_position);
|
||||
float2 dy = dfdy(input.st_position);
|
||||
float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
|
||||
(2. * input.st_position.x) * dy.x - dy.y);
|
||||
float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
|
||||
float distance = f / length(gradient);
|
||||
float alpha = saturate(0.5 - distance);
|
||||
return float4(alpha, 0., 0., 1.);
|
||||
}
|
||||
|
||||
struct PathSpriteVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 tile_position;
|
||||
float4 color [[flat]];
|
||||
};
|
||||
|
||||
vertex PathSpriteVertexOutput path_sprite_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
|
||||
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(SpriteInputIndex_ViewportSize)]],
|
||||
constant Size_DevicePixels *atlas_size
|
||||
[[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
|
||||
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
PathSprite sprite = sprites[sprite_id];
|
||||
// Don't apply content mask because it was already accounted for when
|
||||
// rasterizing the path.
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, sprite.bounds, viewport_size);
|
||||
float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
|
||||
float4 color = hsla_to_rgba(sprite.color);
|
||||
return PathSpriteVertexOutput{device_position, tile_position, color};
|
||||
}
|
||||
|
||||
fragment float4 path_sprite_fragment(
|
||||
PathSpriteVertexOutput input [[stage_in]],
|
||||
constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
|
||||
texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
|
||||
constexpr sampler atlas_texture_sampler(mag_filter::linear,
|
||||
min_filter::linear);
|
||||
float4 sample =
|
||||
atlas_texture.sample(atlas_texture_sampler, input.tile_position);
|
||||
float mask = 1. - abs(1. - fmod(sample.r, 2.));
|
||||
float4 color = input.color;
|
||||
color.a *= mask;
|
||||
return color;
|
||||
}
|
||||
|
||||
struct SurfaceVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 texture_position;
|
||||
float clip_distance [[clip_distance]][4];
|
||||
};
|
||||
|
||||
struct SurfaceFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 texture_position;
|
||||
};
|
||||
|
||||
vertex SurfaceVertexOutput surface_vertex(
|
||||
uint unit_vertex_id [[vertex_id]], uint surface_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(SurfaceInputIndex_Vertices)]],
|
||||
constant SurfaceBounds *surfaces [[buffer(SurfaceInputIndex_Surfaces)]],
|
||||
constant Size_DevicePixels *viewport_size
|
||||
[[buffer(SurfaceInputIndex_ViewportSize)]],
|
||||
constant Size_DevicePixels *texture_size
|
||||
[[buffer(SurfaceInputIndex_TextureSize)]]) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
SurfaceBounds surface = surfaces[surface_id];
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, surface.bounds, viewport_size);
|
||||
float4 clip_distance = distance_from_clip_rect(unit_vertex, surface.bounds,
|
||||
surface.content_mask.bounds);
|
||||
// We are going to copy the whole texture, so the texture position corresponds
|
||||
// to the current vertex of the unit triangle.
|
||||
float2 texture_position = unit_vertex;
|
||||
return SurfaceVertexOutput{
|
||||
device_position,
|
||||
texture_position,
|
||||
{clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
|
||||
}
|
||||
|
||||
fragment float4 surface_fragment(SurfaceFragmentInput input [[stage_in]],
|
||||
texture2d<float> y_texture
|
||||
[[texture(SurfaceInputIndex_YTexture)]],
|
||||
texture2d<float> cb_cr_texture
|
||||
[[texture(SurfaceInputIndex_CbCrTexture)]]) {
|
||||
constexpr sampler texture_sampler(mag_filter::linear, min_filter::linear);
|
||||
const float4x4 ycbcrToRGBTransform =
|
||||
float4x4(float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
|
||||
float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
|
||||
float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
|
||||
float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f));
|
||||
float4 ycbcr = float4(
|
||||
y_texture.sample(texture_sampler, input.texture_position).r,
|
||||
cb_cr_texture.sample(texture_sampler, input.texture_position).rg, 1.0);
|
||||
|
||||
return ycbcrToRGBTransform * ycbcr;
|
||||
}
|
||||
|
||||
float4 hsla_to_rgba(Hsla hsla) {
|
||||
float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
|
||||
float s = hsla.s;
|
||||
float l = hsla.l;
|
||||
float a = hsla.a;
|
||||
|
||||
float c = (1.0 - fabs(2.0 * l - 1.0)) * s;
|
||||
float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
|
||||
float m = l - c / 2.0;
|
||||
|
||||
float r = 0.0;
|
||||
float g = 0.0;
|
||||
float b = 0.0;
|
||||
|
||||
if (h >= 0.0 && h < 1.0) {
|
||||
r = c;
|
||||
g = x;
|
||||
b = 0.0;
|
||||
} else if (h >= 1.0 && h < 2.0) {
|
||||
r = x;
|
||||
g = c;
|
||||
b = 0.0;
|
||||
} else if (h >= 2.0 && h < 3.0) {
|
||||
r = 0.0;
|
||||
g = c;
|
||||
b = x;
|
||||
} else if (h >= 3.0 && h < 4.0) {
|
||||
r = 0.0;
|
||||
g = x;
|
||||
b = c;
|
||||
} else if (h >= 4.0 && h < 5.0) {
|
||||
r = x;
|
||||
g = 0.0;
|
||||
b = c;
|
||||
} else {
|
||||
r = c;
|
||||
g = 0.0;
|
||||
b = x;
|
||||
}
|
||||
|
||||
float4 rgba;
|
||||
rgba.x = (r + m);
|
||||
rgba.y = (g + m);
|
||||
rgba.z = (b + m);
|
||||
rgba.w = a;
|
||||
return rgba;
|
||||
}
|
||||
|
||||
float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
constant Size_DevicePixels *input_viewport_size) {
|
||||
float2 position =
|
||||
unit_vertex * float2(bounds.size.width, bounds.size.height) +
|
||||
float2(bounds.origin.x, bounds.origin.y);
|
||||
float2 viewport_size = float2((float)input_viewport_size->width,
|
||||
(float)input_viewport_size->height);
|
||||
float2 device_position =
|
||||
position / viewport_size * float2(2., -2.) + float2(-1., 1.);
|
||||
return float4(device_position, 0., 1.);
|
||||
}
|
||||
|
||||
float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
|
||||
constant Size_DevicePixels *atlas_size) {
|
||||
float2 tile_origin = float2(tile.bounds.origin.x, tile.bounds.origin.y);
|
||||
float2 tile_size = float2(tile.bounds.size.width, tile.bounds.size.height);
|
||||
return (tile_origin + unit_vertex * tile_size) /
|
||||
float2((float)atlas_size->width, (float)atlas_size->height);
|
||||
}
|
||||
|
||||
float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
|
||||
Corners_ScaledPixels corner_radii) {
|
||||
float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
|
||||
float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
|
||||
float2 center_to_point = point - center;
|
||||
float corner_radius;
|
||||
if (center_to_point.x < 0.) {
|
||||
if (center_to_point.y < 0.) {
|
||||
corner_radius = corner_radii.top_left;
|
||||
} else {
|
||||
corner_radius = corner_radii.bottom_left;
|
||||
}
|
||||
} else {
|
||||
if (center_to_point.y < 0.) {
|
||||
corner_radius = corner_radii.top_right;
|
||||
} else {
|
||||
corner_radius = corner_radii.bottom_right;
|
||||
}
|
||||
}
|
||||
|
||||
float2 rounded_edge_to_point =
|
||||
abs(center_to_point) - half_size + corner_radius;
|
||||
float distance =
|
||||
length(max(0., rounded_edge_to_point)) +
|
||||
min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
|
||||
corner_radius;
|
||||
|
||||
return distance;
|
||||
}
|
||||
|
||||
// A standard gaussian function, used for weighting samples
|
||||
float gaussian(float x, float sigma) {
|
||||
return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
|
||||
}
|
||||
|
||||
// This approximates the error function, needed for the gaussian integral
|
||||
float2 erf(float2 x) {
|
||||
float2 s = sign(x);
|
||||
float2 a = abs(x);
|
||||
x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
|
||||
x *= x;
|
||||
return s - s / (x * x);
|
||||
}
|
||||
|
||||
float blur_along_x(float x, float y, float sigma, float corner,
|
||||
float2 half_size) {
|
||||
float delta = min(half_size.y - corner - abs(y), 0.);
|
||||
float curved =
|
||||
half_size.x - corner + sqrt(max(0., corner * corner - delta * delta));
|
||||
float2 integral =
|
||||
0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
|
||||
return integral.y - integral.x;
|
||||
}
|
||||
|
||||
float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
|
||||
Bounds_ScaledPixels clip_bounds) {
|
||||
float2 position =
|
||||
unit_vertex * float2(bounds.size.width, bounds.size.height) +
|
||||
float2(bounds.origin.x, bounds.origin.y);
|
||||
return float4(position.x - clip_bounds.origin.x,
|
||||
clip_bounds.origin.x + clip_bounds.size.width - position.x,
|
||||
position.y - clip_bounds.origin.y,
|
||||
clip_bounds.origin.y + clip_bounds.size.height - position.y);
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
#include <simd/simd.h>
|
||||
|
||||
typedef struct {
|
||||
vector_float2 viewport_size;
|
||||
} GPUIUniforms;
|
||||
|
||||
typedef enum {
|
||||
GPUIQuadInputIndexVertices = 0,
|
||||
GPUIQuadInputIndexQuads = 1,
|
||||
GPUIQuadInputIndexUniforms = 2,
|
||||
} GPUIQuadInputIndex;
|
||||
|
||||
typedef struct {
|
||||
vector_float2 origin;
|
||||
vector_float2 size;
|
||||
vector_uchar4 background_color;
|
||||
float border_top;
|
||||
float border_right;
|
||||
float border_bottom;
|
||||
float border_left;
|
||||
vector_uchar4 border_color;
|
||||
float corner_radius_top_left;
|
||||
float corner_radius_top_right;
|
||||
float corner_radius_bottom_right;
|
||||
float corner_radius_bottom_left;
|
||||
} GPUIQuad;
|
||||
|
||||
typedef enum {
|
||||
GPUIShadowInputIndexVertices = 0,
|
||||
GPUIShadowInputIndexShadows = 1,
|
||||
GPUIShadowInputIndexUniforms = 2,
|
||||
} GPUIShadowInputIndex;
|
||||
|
||||
typedef struct {
|
||||
vector_float2 origin;
|
||||
vector_float2 size;
|
||||
float corner_radius_top_left;
|
||||
float corner_radius_top_right;
|
||||
float corner_radius_bottom_right;
|
||||
float corner_radius_bottom_left;
|
||||
float sigma;
|
||||
vector_uchar4 color;
|
||||
} GPUIShadow;
|
||||
|
||||
typedef enum {
|
||||
GPUISpriteVertexInputIndexVertices = 0,
|
||||
GPUISpriteVertexInputIndexSprites = 1,
|
||||
GPUISpriteVertexInputIndexViewportSize = 2,
|
||||
GPUISpriteVertexInputIndexAtlasSize = 3,
|
||||
} GPUISpriteVertexInputIndex;
|
||||
|
||||
typedef enum {
|
||||
GPUISpriteFragmentInputIndexAtlas = 0,
|
||||
} GPUISpriteFragmentInputIndex;
|
||||
|
||||
typedef struct {
|
||||
vector_float2 origin;
|
||||
vector_float2 target_size;
|
||||
vector_float2 source_size;
|
||||
vector_float2 atlas_origin;
|
||||
vector_uchar4 color;
|
||||
uint8_t compute_winding;
|
||||
} GPUISprite;
|
||||
|
||||
typedef enum {
|
||||
GPUIPathAtlasVertexInputIndexVertices = 0,
|
||||
GPUIPathAtlasVertexInputIndexAtlasSize = 1,
|
||||
} GPUIPathAtlasVertexInputIndex;
|
||||
|
||||
typedef struct {
|
||||
vector_float2 xy_position;
|
||||
vector_float2 st_position;
|
||||
vector_float2 clip_rect_origin;
|
||||
vector_float2 clip_rect_size;
|
||||
} GPUIPathVertex;
|
||||
|
||||
typedef enum {
|
||||
GPUIImageVertexInputIndexVertices = 0,
|
||||
GPUIImageVertexInputIndexImages = 1,
|
||||
GPUIImageVertexInputIndexViewportSize = 2,
|
||||
GPUIImageVertexInputIndexAtlasSize = 3,
|
||||
} GPUIImageVertexInputIndex;
|
||||
|
||||
typedef enum {
|
||||
GPUIImageFragmentInputIndexAtlas = 0,
|
||||
} GPUIImageFragmentInputIndex;
|
||||
|
||||
typedef struct {
|
||||
vector_float2 origin;
|
||||
vector_float2 target_size;
|
||||
vector_float2 source_size;
|
||||
vector_float2 atlas_origin;
|
||||
float border_top;
|
||||
float border_right;
|
||||
float border_bottom;
|
||||
float border_left;
|
||||
vector_uchar4 border_color;
|
||||
float corner_radius_top_left;
|
||||
float corner_radius_top_right;
|
||||
float corner_radius_bottom_right;
|
||||
float corner_radius_bottom_left;
|
||||
uint8_t grayscale;
|
||||
} GPUIImage;
|
||||
|
||||
typedef enum {
|
||||
GPUISurfaceVertexInputIndexVertices = 0,
|
||||
GPUISurfaceVertexInputIndexSurfaces = 1,
|
||||
GPUISurfaceVertexInputIndexViewportSize = 2,
|
||||
GPUISurfaceVertexInputIndexAtlasSize = 3,
|
||||
} GPUISurfaceVertexInputIndex;
|
||||
|
||||
typedef enum {
|
||||
GPUISurfaceFragmentInputIndexYAtlas = 0,
|
||||
GPUISurfaceFragmentInputIndexCbCrAtlas = 1,
|
||||
} GPUISurfaceFragmentInputIndex;
|
||||
|
||||
typedef struct {
|
||||
vector_float2 origin;
|
||||
vector_float2 target_size;
|
||||
vector_float2 source_size;
|
||||
} GPUISurface;
|
||||
|
||||
typedef enum {
|
||||
GPUIUnderlineInputIndexVertices = 0,
|
||||
GPUIUnderlineInputIndexUnderlines = 1,
|
||||
GPUIUnderlineInputIndexUniforms = 2,
|
||||
} GPUIUnderlineInputIndex;
|
||||
|
||||
typedef struct {
|
||||
vector_float2 origin;
|
||||
vector_float2 size;
|
||||
float thickness;
|
||||
vector_uchar4 color;
|
||||
uint8_t squiggly;
|
||||
} GPUIUnderline;
|
|
@ -1,464 +0,0 @@
|
|||
#include <metal_stdlib>
|
||||
#include "shaders.h"
|
||||
|
||||
using namespace metal;
|
||||
|
||||
float4 coloru_to_colorf(uchar4 coloru) {
|
||||
return float4(coloru) / float4(0xff, 0xff, 0xff, 0xff);
|
||||
}
|
||||
|
||||
float4 to_device_position(float2 pixel_position, float2 viewport_size) {
|
||||
return float4(pixel_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.);
|
||||
}
|
||||
|
||||
// A standard gaussian function, used for weighting samples
|
||||
float gaussian(float x, float sigma) {
|
||||
return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
|
||||
}
|
||||
|
||||
// This approximates the error function, needed for the gaussian integral
|
||||
float2 erf(float2 x) {
|
||||
float2 s = sign(x);
|
||||
float2 a = abs(x);
|
||||
x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
|
||||
x *= x;
|
||||
return s - s / (x * x);
|
||||
}
|
||||
|
||||
float blur_along_x(float x, float y, float sigma, float corner, float2 halfSize) {
|
||||
float delta = min(halfSize.y - corner - abs(y), 0.);
|
||||
float curved = halfSize.x - corner + sqrt(max(0., corner * corner - delta * delta));
|
||||
float2 integral = 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
|
||||
return integral.y - integral.x;
|
||||
}
|
||||
|
||||
struct QuadFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 atlas_position; // only used in the image shader
|
||||
float2 origin;
|
||||
float2 size;
|
||||
float4 background_color;
|
||||
float border_top;
|
||||
float border_right;
|
||||
float border_bottom;
|
||||
float border_left;
|
||||
float4 border_color;
|
||||
float corner_radius_top_left;
|
||||
float corner_radius_top_right;
|
||||
float corner_radius_bottom_right;
|
||||
float corner_radius_bottom_left;
|
||||
uchar grayscale; // only used in image shader
|
||||
};
|
||||
|
||||
float4 quad_sdf(QuadFragmentInput input) {
|
||||
float2 half_size = input.size / 2.;
|
||||
float2 center = input.origin + half_size;
|
||||
float2 center_to_point = input.position.xy - center;
|
||||
float corner_radius;
|
||||
if (center_to_point.x < 0.) {
|
||||
if (center_to_point.y < 0.) {
|
||||
corner_radius = input.corner_radius_top_left;
|
||||
} else {
|
||||
corner_radius = input.corner_radius_bottom_left;
|
||||
}
|
||||
} else {
|
||||
if (center_to_point.y < 0.) {
|
||||
corner_radius = input.corner_radius_top_right;
|
||||
} else {
|
||||
corner_radius = input.corner_radius_bottom_right;
|
||||
}
|
||||
}
|
||||
|
||||
float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
|
||||
float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
|
||||
|
||||
float vertical_border = center_to_point.x <= 0. ? input.border_left : input.border_right;
|
||||
float horizontal_border = center_to_point.y <= 0. ? input.border_top : input.border_bottom;
|
||||
float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
|
||||
float2 point_to_inset_corner = abs(center_to_point) - inset_size;
|
||||
float border_width;
|
||||
if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
|
||||
border_width = 0.;
|
||||
} else if (point_to_inset_corner.y > point_to_inset_corner.x) {
|
||||
border_width = horizontal_border;
|
||||
} else {
|
||||
border_width = vertical_border;
|
||||
}
|
||||
|
||||
float4 color;
|
||||
if (border_width == 0.) {
|
||||
color = input.background_color;
|
||||
} else {
|
||||
float inset_distance = distance + border_width;
|
||||
|
||||
// Decrease border's opacity as we move inside the background.
|
||||
input.border_color.a *= 1. - saturate(0.5 - inset_distance);
|
||||
|
||||
// Alpha-blend the border and the background.
|
||||
float output_alpha = input.border_color.a + input.background_color.a * (1. - input.border_color.a);
|
||||
float3 premultiplied_border_rgb = input.border_color.rgb * input.border_color.a;
|
||||
float3 premultiplied_background_rgb = input.background_color.rgb * input.background_color.a;
|
||||
float3 premultiplied_output_rgb = premultiplied_border_rgb + premultiplied_background_rgb * (1. - input.border_color.a);
|
||||
color = float4(premultiplied_output_rgb / output_alpha, output_alpha);
|
||||
}
|
||||
|
||||
return color * float4(1., 1., 1., saturate(0.5 - distance));
|
||||
}
|
||||
|
||||
vertex QuadFragmentInput quad_vertex(
|
||||
uint unit_vertex_id [[vertex_id]],
|
||||
uint quad_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(GPUIQuadInputIndexVertices)]],
|
||||
constant GPUIQuad *quads [[buffer(GPUIQuadInputIndexQuads)]],
|
||||
constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
|
||||
) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
GPUIQuad quad = quads[quad_id];
|
||||
float2 position = unit_vertex * quad.size + quad.origin;
|
||||
float4 device_position = to_device_position(position, uniforms->viewport_size);
|
||||
|
||||
return QuadFragmentInput {
|
||||
device_position,
|
||||
float2(0., 0.),
|
||||
quad.origin,
|
||||
quad.size,
|
||||
coloru_to_colorf(quad.background_color),
|
||||
quad.border_top,
|
||||
quad.border_right,
|
||||
quad.border_bottom,
|
||||
quad.border_left,
|
||||
coloru_to_colorf(quad.border_color),
|
||||
quad.corner_radius_top_left,
|
||||
quad.corner_radius_top_right,
|
||||
quad.corner_radius_bottom_right,
|
||||
quad.corner_radius_bottom_left,
|
||||
0,
|
||||
};
|
||||
}
|
||||
|
||||
fragment float4 quad_fragment(
|
||||
QuadFragmentInput input [[stage_in]]
|
||||
) {
|
||||
return quad_sdf(input);
|
||||
}
|
||||
|
||||
struct ShadowFragmentInput {
|
||||
float4 position [[position]];
|
||||
vector_float2 origin;
|
||||
vector_float2 size;
|
||||
float corner_radius_top_left;
|
||||
float corner_radius_top_right;
|
||||
float corner_radius_bottom_right;
|
||||
float corner_radius_bottom_left;
|
||||
float sigma;
|
||||
vector_uchar4 color;
|
||||
};
|
||||
|
||||
vertex ShadowFragmentInput shadow_vertex(
|
||||
uint unit_vertex_id [[vertex_id]],
|
||||
uint shadow_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(GPUIShadowInputIndexVertices)]],
|
||||
constant GPUIShadow *shadows [[buffer(GPUIShadowInputIndexShadows)]],
|
||||
constant GPUIUniforms *uniforms [[buffer(GPUIShadowInputIndexUniforms)]]
|
||||
) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
GPUIShadow shadow = shadows[shadow_id];
|
||||
|
||||
float margin = 3. * shadow.sigma;
|
||||
float2 position = unit_vertex * (shadow.size + 2. * margin) + shadow.origin - margin;
|
||||
float4 device_position = to_device_position(position, uniforms->viewport_size);
|
||||
|
||||
return ShadowFragmentInput {
|
||||
device_position,
|
||||
shadow.origin,
|
||||
shadow.size,
|
||||
shadow.corner_radius_top_left,
|
||||
shadow.corner_radius_top_right,
|
||||
shadow.corner_radius_bottom_right,
|
||||
shadow.corner_radius_bottom_left,
|
||||
shadow.sigma,
|
||||
shadow.color,
|
||||
};
|
||||
}
|
||||
|
||||
fragment float4 shadow_fragment(
|
||||
ShadowFragmentInput input [[stage_in]]
|
||||
) {
|
||||
float sigma = input.sigma;
|
||||
float2 half_size = input.size / 2.;
|
||||
float2 center = input.origin + half_size;
|
||||
float2 point = input.position.xy - center;
|
||||
float2 center_to_point = input.position.xy - center;
|
||||
float corner_radius;
|
||||
if (center_to_point.x < 0.) {
|
||||
if (center_to_point.y < 0.) {
|
||||
corner_radius = input.corner_radius_top_left;
|
||||
} else {
|
||||
corner_radius = input.corner_radius_bottom_left;
|
||||
}
|
||||
} else {
|
||||
if (center_to_point.y < 0.) {
|
||||
corner_radius = input.corner_radius_top_right;
|
||||
} else {
|
||||
corner_radius = input.corner_radius_bottom_right;
|
||||
}
|
||||
}
|
||||
|
||||
// The signal is only non-zero in a limited range, so don't waste samples
|
||||
float low = point.y - half_size.y;
|
||||
float high = point.y + half_size.y;
|
||||
float start = clamp(-3. * sigma, low, high);
|
||||
float end = clamp(3. * sigma, low, high);
|
||||
|
||||
// Accumulate samples (we can get away with surprisingly few samples)
|
||||
float step = (end - start) / 4.;
|
||||
float y = start + step * 0.5;
|
||||
float alpha = 0.;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
alpha += blur_along_x(point.x, point.y - y, sigma, corner_radius, half_size) * gaussian(y, sigma) * step;
|
||||
y += step;
|
||||
}
|
||||
|
||||
return float4(1., 1., 1., alpha) * coloru_to_colorf(input.color);
|
||||
}
|
||||
|
||||
struct SpriteFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 atlas_position;
|
||||
float4 color [[flat]];
|
||||
uchar compute_winding [[flat]];
|
||||
};
|
||||
|
||||
vertex SpriteFragmentInput sprite_vertex(
|
||||
uint unit_vertex_id [[vertex_id]],
|
||||
uint sprite_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(GPUISpriteVertexInputIndexVertices)]],
|
||||
constant GPUISprite *sprites [[buffer(GPUISpriteVertexInputIndexSprites)]],
|
||||
constant float2 *viewport_size [[buffer(GPUISpriteVertexInputIndexViewportSize)]],
|
||||
constant float2 *atlas_size [[buffer(GPUISpriteVertexInputIndexAtlasSize)]]
|
||||
) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
GPUISprite sprite = sprites[sprite_id];
|
||||
float2 position = unit_vertex * sprite.target_size + sprite.origin;
|
||||
float4 device_position = to_device_position(position, *viewport_size);
|
||||
float2 atlas_position = (unit_vertex * sprite.source_size + sprite.atlas_origin) / *atlas_size;
|
||||
|
||||
return SpriteFragmentInput {
|
||||
device_position,
|
||||
atlas_position,
|
||||
coloru_to_colorf(sprite.color),
|
||||
sprite.compute_winding
|
||||
};
|
||||
}
|
||||
|
||||
fragment float4 sprite_fragment(
|
||||
SpriteFragmentInput input [[stage_in]],
|
||||
texture2d<float> atlas [[ texture(GPUISpriteFragmentInputIndexAtlas) ]]
|
||||
) {
|
||||
constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
|
||||
float4 color = input.color;
|
||||
float4 sample = atlas.sample(atlas_sampler, input.atlas_position);
|
||||
float mask;
|
||||
if (input.compute_winding) {
|
||||
mask = 1. - abs(1. - fmod(sample.r, 2.));
|
||||
} else {
|
||||
mask = sample.a;
|
||||
}
|
||||
color.a *= mask;
|
||||
return color;
|
||||
}
|
||||
|
||||
vertex QuadFragmentInput image_vertex(
|
||||
uint unit_vertex_id [[vertex_id]],
|
||||
uint image_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(GPUIImageVertexInputIndexVertices)]],
|
||||
constant GPUIImage *images [[buffer(GPUIImageVertexInputIndexImages)]],
|
||||
constant float2 *viewport_size [[buffer(GPUIImageVertexInputIndexViewportSize)]],
|
||||
constant float2 *atlas_size [[buffer(GPUIImageVertexInputIndexAtlasSize)]]
|
||||
) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
GPUIImage image = images[image_id];
|
||||
float2 position = unit_vertex * image.target_size + image.origin;
|
||||
float4 device_position = to_device_position(position, *viewport_size);
|
||||
float2 atlas_position = (unit_vertex * image.source_size + image.atlas_origin) / *atlas_size;
|
||||
|
||||
return QuadFragmentInput {
|
||||
device_position,
|
||||
atlas_position,
|
||||
image.origin,
|
||||
image.target_size,
|
||||
float4(0.),
|
||||
image.border_top,
|
||||
image.border_right,
|
||||
image.border_bottom,
|
||||
image.border_left,
|
||||
coloru_to_colorf(image.border_color),
|
||||
image.corner_radius_top_left,
|
||||
image.corner_radius_top_right,
|
||||
image.corner_radius_bottom_right,
|
||||
image.corner_radius_bottom_left,
|
||||
image.grayscale,
|
||||
};
|
||||
}
|
||||
|
||||
fragment float4 image_fragment(
|
||||
QuadFragmentInput input [[stage_in]],
|
||||
texture2d<float> atlas [[ texture(GPUIImageFragmentInputIndexAtlas) ]]
|
||||
) {
|
||||
constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
|
||||
input.background_color = atlas.sample(atlas_sampler, input.atlas_position);
|
||||
if (input.grayscale) {
|
||||
float grayscale =
|
||||
0.2126 * input.background_color.r +
|
||||
0.7152 * input.background_color.g +
|
||||
0.0722 * input.background_color.b;
|
||||
input.background_color = float4(grayscale, grayscale, grayscale, input.background_color.a);
|
||||
}
|
||||
return quad_sdf(input);
|
||||
}
|
||||
|
||||
vertex QuadFragmentInput surface_vertex(
|
||||
uint unit_vertex_id [[vertex_id]],
|
||||
uint image_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(GPUISurfaceVertexInputIndexVertices)]],
|
||||
constant GPUISurface *images [[buffer(GPUISurfaceVertexInputIndexSurfaces)]],
|
||||
constant float2 *viewport_size [[buffer(GPUISurfaceVertexInputIndexViewportSize)]],
|
||||
constant float2 *atlas_size [[buffer(GPUISurfaceVertexInputIndexAtlasSize)]]
|
||||
) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
GPUISurface image = images[image_id];
|
||||
float2 position = unit_vertex * image.target_size + image.origin;
|
||||
float4 device_position = to_device_position(position, *viewport_size);
|
||||
float2 atlas_position = (unit_vertex * image.source_size) / *atlas_size;
|
||||
|
||||
return QuadFragmentInput {
|
||||
device_position,
|
||||
atlas_position,
|
||||
image.origin,
|
||||
image.target_size,
|
||||
float4(0.),
|
||||
0.,
|
||||
0.,
|
||||
0.,
|
||||
0.,
|
||||
float4(0.),
|
||||
0.,
|
||||
0,
|
||||
};
|
||||
}
|
||||
|
||||
fragment float4 surface_fragment(
|
||||
QuadFragmentInput input [[stage_in]],
|
||||
texture2d<float> y_atlas [[ texture(GPUISurfaceFragmentInputIndexYAtlas) ]],
|
||||
texture2d<float> cb_cr_atlas [[ texture(GPUISurfaceFragmentInputIndexCbCrAtlas) ]]
|
||||
) {
|
||||
constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
|
||||
const float4x4 ycbcrToRGBTransform = float4x4(
|
||||
float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
|
||||
float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
|
||||
float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
|
||||
float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f)
|
||||
);
|
||||
float4 ycbcr = float4(y_atlas.sample(atlas_sampler, input.atlas_position).r,
|
||||
cb_cr_atlas.sample(atlas_sampler, input.atlas_position).rg, 1.0);
|
||||
|
||||
input.background_color = ycbcrToRGBTransform * ycbcr;
|
||||
return quad_sdf(input);
|
||||
}
|
||||
|
||||
struct PathAtlasVertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 st_position;
|
||||
float clip_rect_distance [[clip_distance]] [4];
|
||||
};
|
||||
|
||||
struct PathAtlasFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 st_position;
|
||||
};
|
||||
|
||||
vertex PathAtlasVertexOutput path_atlas_vertex(
|
||||
uint vertex_id [[vertex_id]],
|
||||
constant GPUIPathVertex *vertices [[buffer(GPUIPathAtlasVertexInputIndexVertices)]],
|
||||
constant float2 *atlas_size [[buffer(GPUIPathAtlasVertexInputIndexAtlasSize)]]
|
||||
) {
|
||||
GPUIPathVertex v = vertices[vertex_id];
|
||||
float4 device_position = to_device_position(v.xy_position, *atlas_size);
|
||||
return PathAtlasVertexOutput {
|
||||
device_position,
|
||||
v.st_position,
|
||||
{
|
||||
v.xy_position.x - v.clip_rect_origin.x,
|
||||
v.clip_rect_origin.x + v.clip_rect_size.x - v.xy_position.x,
|
||||
v.xy_position.y - v.clip_rect_origin.y,
|
||||
v.clip_rect_origin.y + v.clip_rect_size.y - v.xy_position.y
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fragment float4 path_atlas_fragment(
|
||||
PathAtlasFragmentInput input [[stage_in]]
|
||||
) {
|
||||
float2 dx = dfdx(input.st_position);
|
||||
float2 dy = dfdy(input.st_position);
|
||||
float2 gradient = float2(
|
||||
(2. * input.st_position.x) * dx.x - dx.y,
|
||||
(2. * input.st_position.x) * dy.x - dy.y
|
||||
);
|
||||
float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
|
||||
float distance = f / length(gradient);
|
||||
float alpha = saturate(0.5 - distance);
|
||||
return float4(alpha, 0., 0., 1.);
|
||||
}
|
||||
|
||||
struct UnderlineFragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 origin;
|
||||
float2 size;
|
||||
float thickness;
|
||||
float4 color;
|
||||
bool squiggly;
|
||||
};
|
||||
|
||||
vertex UnderlineFragmentInput underline_vertex(
|
||||
uint unit_vertex_id [[vertex_id]],
|
||||
uint underline_id [[instance_id]],
|
||||
constant float2 *unit_vertices [[buffer(GPUIUnderlineInputIndexVertices)]],
|
||||
constant GPUIUnderline *underlines [[buffer(GPUIUnderlineInputIndexUnderlines)]],
|
||||
constant GPUIUniforms *uniforms [[buffer(GPUIUnderlineInputIndexUniforms)]]
|
||||
) {
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
GPUIUnderline underline = underlines[underline_id];
|
||||
float2 position = unit_vertex * underline.size + underline.origin;
|
||||
float4 device_position = to_device_position(position, uniforms->viewport_size);
|
||||
|
||||
return UnderlineFragmentInput {
|
||||
device_position,
|
||||
underline.origin,
|
||||
underline.size,
|
||||
underline.thickness,
|
||||
coloru_to_colorf(underline.color),
|
||||
underline.squiggly != 0,
|
||||
};
|
||||
}
|
||||
|
||||
fragment float4 underline_fragment(
|
||||
UnderlineFragmentInput input [[stage_in]]
|
||||
) {
|
||||
if (input.squiggly) {
|
||||
float half_thickness = input.thickness * 0.5;
|
||||
float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5);
|
||||
float frequency = (M_PI_F * (3. * input.thickness)) / 8.;
|
||||
float amplitude = 1. / (2. * input.thickness);
|
||||
float sine = sin(st.x * frequency) * amplitude;
|
||||
float dSine = cos(st.x * frequency) * amplitude * frequency;
|
||||
float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
|
||||
float distance_in_pixels = distance * input.size.y;
|
||||
float distance_from_top_border = distance_in_pixels - half_thickness;
|
||||
float distance_from_bottom_border = distance_in_pixels + half_thickness;
|
||||
float alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border));
|
||||
return input.color * float4(1., 1., 1., alpha);
|
||||
} else {
|
||||
return input.color;
|
||||
}
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
use super::atlas::AtlasAllocator;
|
||||
use crate::{
|
||||
fonts::{FontId, GlyphId},
|
||||
geometry::vector::{vec2f, Vector2F, Vector2I},
|
||||
platform::{self, RasterizationOptions},
|
||||
};
|
||||
use collections::hash_map::Entry;
|
||||
use metal::{MTLPixelFormat, TextureDescriptor};
|
||||
use ordered_float::OrderedFloat;
|
||||
use std::{borrow::Cow, collections::HashMap, sync::Arc};
|
||||
|
||||
#[derive(Hash, Eq, PartialEq)]
|
||||
struct GlyphDescriptor {
|
||||
font_id: FontId,
|
||||
font_size: OrderedFloat<f32>,
|
||||
glyph_id: GlyphId,
|
||||
subpixel_variant: (u8, u8),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GlyphSprite {
|
||||
pub atlas_id: usize,
|
||||
pub atlas_origin: Vector2I,
|
||||
pub offset: Vector2I,
|
||||
pub size: Vector2I,
|
||||
}
|
||||
|
||||
#[derive(Hash, Eq, PartialEq)]
|
||||
struct IconDescriptor {
|
||||
path: Cow<'static, str>,
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct IconSprite {
|
||||
pub atlas_id: usize,
|
||||
pub atlas_origin: Vector2I,
|
||||
pub size: Vector2I,
|
||||
}
|
||||
|
||||
pub struct SpriteCache {
|
||||
fonts: Arc<dyn platform::FontSystem>,
|
||||
atlases: AtlasAllocator,
|
||||
glyphs: HashMap<GlyphDescriptor, Option<GlyphSprite>>,
|
||||
icons: HashMap<IconDescriptor, IconSprite>,
|
||||
scale_factor: f32,
|
||||
}
|
||||
|
||||
impl SpriteCache {
|
||||
pub fn new(
|
||||
device: metal::Device,
|
||||
size: Vector2I,
|
||||
scale_factor: f32,
|
||||
fonts: Arc<dyn platform::FontSystem>,
|
||||
) -> Self {
|
||||
let descriptor = TextureDescriptor::new();
|
||||
descriptor.set_pixel_format(MTLPixelFormat::A8Unorm);
|
||||
descriptor.set_width(size.x() as u64);
|
||||
descriptor.set_height(size.y() as u64);
|
||||
Self {
|
||||
fonts,
|
||||
atlases: AtlasAllocator::new(device, descriptor),
|
||||
glyphs: Default::default(),
|
||||
icons: Default::default(),
|
||||
scale_factor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_scale_factor(&mut self, scale_factor: f32) {
|
||||
if scale_factor != self.scale_factor {
|
||||
self.icons.clear();
|
||||
self.glyphs.clear();
|
||||
self.atlases.clear();
|
||||
}
|
||||
self.scale_factor = scale_factor;
|
||||
}
|
||||
|
||||
pub fn render_glyph(
|
||||
&mut self,
|
||||
font_id: FontId,
|
||||
font_size: f32,
|
||||
glyph_id: GlyphId,
|
||||
target_position: Vector2F,
|
||||
) -> Option<GlyphSprite> {
|
||||
const SUBPIXEL_VARIANTS: u8 = 4;
|
||||
|
||||
let target_position = target_position * self.scale_factor;
|
||||
let subpixel_variant = (
|
||||
(target_position.x().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
|
||||
(target_position.y().fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
|
||||
);
|
||||
|
||||
self.glyphs
|
||||
.entry(GlyphDescriptor {
|
||||
font_id,
|
||||
font_size: OrderedFloat(font_size),
|
||||
glyph_id,
|
||||
subpixel_variant,
|
||||
})
|
||||
.or_insert_with(|| {
|
||||
let subpixel_shift = vec2f(
|
||||
subpixel_variant.0 as f32 / SUBPIXEL_VARIANTS as f32,
|
||||
subpixel_variant.1 as f32 / SUBPIXEL_VARIANTS as f32,
|
||||
);
|
||||
let (glyph_bounds, mask) = self.fonts.rasterize_glyph(
|
||||
font_id,
|
||||
font_size,
|
||||
glyph_id,
|
||||
subpixel_shift,
|
||||
self.scale_factor,
|
||||
RasterizationOptions::Alpha,
|
||||
)?;
|
||||
|
||||
let (alloc_id, atlas_bounds) = self
|
||||
.atlases
|
||||
.upload(glyph_bounds.size(), &mask)
|
||||
.expect("could not upload glyph");
|
||||
Some(GlyphSprite {
|
||||
atlas_id: alloc_id.atlas_id,
|
||||
atlas_origin: atlas_bounds.origin(),
|
||||
offset: glyph_bounds.origin(),
|
||||
size: glyph_bounds.size(),
|
||||
})
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn render_icon(
|
||||
&mut self,
|
||||
size: Vector2I,
|
||||
path: Cow<'static, str>,
|
||||
svg: usvg::Tree,
|
||||
) -> Option<IconSprite> {
|
||||
let atlases = &mut self.atlases;
|
||||
match self.icons.entry(IconDescriptor {
|
||||
path,
|
||||
width: size.x(),
|
||||
height: size.y(),
|
||||
}) {
|
||||
Entry::Occupied(entry) => Some(entry.get().clone()),
|
||||
Entry::Vacant(entry) => {
|
||||
let mut pixmap = tiny_skia::Pixmap::new(size.x() as u32, size.y() as u32)?;
|
||||
resvg::render(&svg, usvg::FitTo::Width(size.x() as u32), pixmap.as_mut());
|
||||
let mask = pixmap
|
||||
.pixels()
|
||||
.iter()
|
||||
.map(|a| a.alpha())
|
||||
.collect::<Vec<_>>();
|
||||
let (alloc_id, atlas_bounds) = atlases.upload(size, &mask)?;
|
||||
let icon_sprite = IconSprite {
|
||||
atlas_id: alloc_id.atlas_id,
|
||||
atlas_origin: atlas_bounds.origin(),
|
||||
size,
|
||||
};
|
||||
Some(entry.insert(icon_sprite).clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn atlas_texture(&self, atlas_id: usize) -> Option<&metal::TextureRef> {
|
||||
self.atlases.texture(atlas_id)
|
||||
}
|
||||
}
|
760
crates/gpui/src/platform/mac/text_system.rs
Normal file
760
crates/gpui/src/platform/mac/text_system.rs
Normal file
|
@ -0,0 +1,760 @@
|
|||
use crate::{
|
||||
point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun,
|
||||
FontStyle, FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point,
|
||||
RenderGlyphParams, Result, ShapedGlyph, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use cocoa::appkit::{CGFloat, CGPoint};
|
||||
use collections::HashMap;
|
||||
use core_foundation::{
|
||||
array::CFIndex,
|
||||
attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
|
||||
base::{CFRange, TCFType},
|
||||
string::CFString,
|
||||
};
|
||||
use core_graphics::{
|
||||
base::{kCGImageAlphaPremultipliedLast, CGGlyph},
|
||||
color_space::CGColorSpace,
|
||||
context::CGContext,
|
||||
};
|
||||
use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
|
||||
use font_kit::{
|
||||
font::Font as FontKitFont,
|
||||
handle::Handle,
|
||||
hinting::HintingOptions,
|
||||
metrics::Metrics,
|
||||
properties::{Style as FontkitStyle, Weight as FontkitWeight},
|
||||
source::SystemSource,
|
||||
sources::mem::MemSource,
|
||||
};
|
||||
use parking_lot::{RwLock, RwLockUpgradableReadGuard};
|
||||
use pathfinder_geometry::{
|
||||
rect::{RectF, RectI},
|
||||
transform2d::Transform2F,
|
||||
vector::{Vector2F, Vector2I},
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
|
||||
|
||||
use super::open_type;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const kCGImageAlphaOnly: u32 = 7;
|
||||
|
||||
pub struct MacTextSystem(RwLock<MacTextSystemState>);
|
||||
|
||||
struct MacTextSystemState {
|
||||
memory_source: MemSource,
|
||||
system_source: SystemSource,
|
||||
fonts: Vec<FontKitFont>,
|
||||
font_selections: HashMap<Font, FontId>,
|
||||
font_ids_by_postscript_name: HashMap<String, FontId>,
|
||||
font_ids_by_family_name: HashMap<SharedString, SmallVec<[FontId; 4]>>,
|
||||
postscript_names_by_font_id: HashMap<FontId, String>,
|
||||
}
|
||||
|
||||
impl MacTextSystem {
|
||||
pub fn new() -> Self {
|
||||
Self(RwLock::new(MacTextSystemState {
|
||||
memory_source: MemSource::empty(),
|
||||
system_source: SystemSource::new(),
|
||||
fonts: Vec::new(),
|
||||
font_selections: HashMap::default(),
|
||||
font_ids_by_postscript_name: HashMap::default(),
|
||||
font_ids_by_family_name: HashMap::default(),
|
||||
postscript_names_by_font_id: HashMap::default(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MacTextSystem {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformTextSystem for MacTextSystem {
|
||||
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
|
||||
self.0.write().add_fonts(fonts)
|
||||
}
|
||||
|
||||
fn all_font_families(&self) -> Vec<String> {
|
||||
self.0
|
||||
.read()
|
||||
.system_source
|
||||
.all_families()
|
||||
.expect("core text should never return an error")
|
||||
}
|
||||
|
||||
fn font_id(&self, font: &Font) -> Result<FontId> {
|
||||
let lock = self.0.upgradable_read();
|
||||
if let Some(font_id) = lock.font_selections.get(font) {
|
||||
Ok(*font_id)
|
||||
} else {
|
||||
let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
|
||||
let candidates = if let Some(font_ids) = lock.font_ids_by_family_name.get(&font.family)
|
||||
{
|
||||
font_ids.as_slice()
|
||||
} else {
|
||||
let font_ids = lock.load_family(&font.family, font.features)?;
|
||||
lock.font_ids_by_family_name
|
||||
.insert(font.family.clone(), font_ids);
|
||||
lock.font_ids_by_family_name[&font.family].as_ref()
|
||||
};
|
||||
|
||||
let candidate_properties = candidates
|
||||
.iter()
|
||||
.map(|font_id| lock.fonts[font_id.0].properties())
|
||||
.collect::<SmallVec<[_; 4]>>();
|
||||
|
||||
let ix = font_kit::matching::find_best_match(
|
||||
&candidate_properties,
|
||||
&font_kit::properties::Properties {
|
||||
style: font.style.into(),
|
||||
weight: font.weight.into(),
|
||||
stretch: Default::default(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let font_id = candidates[ix];
|
||||
lock.font_selections.insert(font.clone(), font_id);
|
||||
Ok(font_id)
|
||||
}
|
||||
}
|
||||
|
||||
fn font_metrics(&self, font_id: FontId) -> FontMetrics {
|
||||
self.0.read().fonts[font_id.0].metrics().into()
|
||||
}
|
||||
|
||||
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
|
||||
Ok(self.0.read().fonts[font_id.0]
|
||||
.typographic_bounds(glyph_id.into())?
|
||||
.into())
|
||||
}
|
||||
|
||||
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
|
||||
self.0.read().advance(font_id, glyph_id)
|
||||
}
|
||||
|
||||
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
|
||||
self.0.read().glyph_for_char(font_id, ch)
|
||||
}
|
||||
|
||||
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||
self.0.read().raster_bounds(params)
|
||||
}
|
||||
|
||||
fn rasterize_glyph(
|
||||
&self,
|
||||
glyph_id: &RenderGlyphParams,
|
||||
raster_bounds: Bounds<DevicePixels>,
|
||||
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
|
||||
self.0.read().rasterize_glyph(glyph_id, raster_bounds)
|
||||
}
|
||||
|
||||
fn layout_line(&self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
|
||||
self.0.write().layout_line(text, font_size, font_runs)
|
||||
}
|
||||
|
||||
fn wrap_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_id: FontId,
|
||||
font_size: Pixels,
|
||||
width: Pixels,
|
||||
) -> Vec<usize> {
|
||||
self.0.read().wrap_line(text, font_id, font_size, width)
|
||||
}
|
||||
}
|
||||
|
||||
impl MacTextSystemState {
|
||||
fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
|
||||
self.memory_source.add_fonts(
|
||||
fonts
|
||||
.iter()
|
||||
.map(|bytes| Handle::from_memory(bytes.clone(), 0)),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_family(
|
||||
&mut self,
|
||||
name: &SharedString,
|
||||
features: FontFeatures,
|
||||
) -> Result<SmallVec<[FontId; 4]>> {
|
||||
let mut font_ids = SmallVec::new();
|
||||
let family = self
|
||||
.memory_source
|
||||
.select_family_by_name(name.as_ref())
|
||||
.or_else(|_| self.system_source.select_family_by_name(name.as_ref()))?;
|
||||
for font in family.fonts() {
|
||||
let mut font = font.load()?;
|
||||
open_type::apply_features(&mut font, features);
|
||||
let font_id = FontId(self.fonts.len());
|
||||
font_ids.push(font_id);
|
||||
let postscript_name = font.postscript_name().unwrap();
|
||||
self.font_ids_by_postscript_name
|
||||
.insert(postscript_name.clone(), font_id);
|
||||
self.postscript_names_by_font_id
|
||||
.insert(font_id, postscript_name);
|
||||
self.fonts.push(font);
|
||||
}
|
||||
Ok(font_ids)
|
||||
}
|
||||
|
||||
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
|
||||
Ok(self.fonts[font_id.0].advance(glyph_id.into())?.into())
|
||||
}
|
||||
|
||||
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
|
||||
self.fonts[font_id.0].glyph_for_char(ch).map(Into::into)
|
||||
}
|
||||
|
||||
fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId {
|
||||
let postscript_name = requested_font.postscript_name();
|
||||
if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) {
|
||||
*font_id
|
||||
} else {
|
||||
let font_id = FontId(self.fonts.len());
|
||||
self.font_ids_by_postscript_name
|
||||
.insert(postscript_name.clone(), font_id);
|
||||
self.postscript_names_by_font_id
|
||||
.insert(font_id, postscript_name);
|
||||
self.fonts
|
||||
.push(font_kit::font::Font::from_core_graphics_font(
|
||||
requested_font.copy_to_CGFont(),
|
||||
));
|
||||
font_id
|
||||
}
|
||||
}
|
||||
|
||||
fn is_emoji(&self, font_id: FontId) -> bool {
|
||||
self.postscript_names_by_font_id
|
||||
.get(&font_id)
|
||||
.map_or(false, |postscript_name| {
|
||||
postscript_name == "AppleColorEmoji"
|
||||
})
|
||||
}
|
||||
|
||||
fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
|
||||
let font = &self.fonts[params.font_id.0];
|
||||
let scale = Transform2F::from_scale(params.scale_factor);
|
||||
Ok(font
|
||||
.raster_bounds(
|
||||
params.glyph_id.into(),
|
||||
params.font_size.into(),
|
||||
scale,
|
||||
HintingOptions::None,
|
||||
font_kit::canvas::RasterizationOptions::GrayscaleAa,
|
||||
)?
|
||||
.into())
|
||||
}
|
||||
|
||||
fn rasterize_glyph(
|
||||
&self,
|
||||
params: &RenderGlyphParams,
|
||||
glyph_bounds: Bounds<DevicePixels>,
|
||||
) -> Result<(Size<DevicePixels>, Vec<u8>)> {
|
||||
if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {
|
||||
Err(anyhow!("glyph bounds are empty"))
|
||||
} else {
|
||||
// Add an extra pixel when the subpixel variant isn't zero to make room for anti-aliasing.
|
||||
let mut bitmap_size = glyph_bounds.size;
|
||||
if params.subpixel_variant.x > 0 {
|
||||
bitmap_size.width += DevicePixels(1);
|
||||
}
|
||||
if params.subpixel_variant.y > 0 {
|
||||
bitmap_size.height += DevicePixels(1);
|
||||
}
|
||||
let bitmap_size = bitmap_size;
|
||||
|
||||
let mut bytes;
|
||||
let cx;
|
||||
if params.is_emoji {
|
||||
bytes = vec![0; bitmap_size.width.0 as usize * 4 * bitmap_size.height.0 as usize];
|
||||
cx = CGContext::create_bitmap_context(
|
||||
Some(bytes.as_mut_ptr() as *mut _),
|
||||
bitmap_size.width.0 as usize,
|
||||
bitmap_size.height.0 as usize,
|
||||
8,
|
||||
bitmap_size.width.0 as usize * 4,
|
||||
&CGColorSpace::create_device_rgb(),
|
||||
kCGImageAlphaPremultipliedLast,
|
||||
);
|
||||
} else {
|
||||
bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];
|
||||
cx = CGContext::create_bitmap_context(
|
||||
Some(bytes.as_mut_ptr() as *mut _),
|
||||
bitmap_size.width.0 as usize,
|
||||
bitmap_size.height.0 as usize,
|
||||
8,
|
||||
bitmap_size.width.0 as usize,
|
||||
&CGColorSpace::create_device_gray(),
|
||||
kCGImageAlphaOnly,
|
||||
);
|
||||
}
|
||||
|
||||
// Move the origin to bottom left and account for scaling, this
|
||||
// makes drawing text consistent with the font-kit's raster_bounds.
|
||||
cx.translate(
|
||||
-glyph_bounds.origin.x.0 as CGFloat,
|
||||
(glyph_bounds.origin.y.0 + glyph_bounds.size.height.0) as CGFloat,
|
||||
);
|
||||
cx.scale(
|
||||
params.scale_factor as CGFloat,
|
||||
params.scale_factor as CGFloat,
|
||||
);
|
||||
|
||||
let subpixel_shift = params
|
||||
.subpixel_variant
|
||||
.map(|v| v as f32 / SUBPIXEL_VARIANTS as f32);
|
||||
cx.set_allows_font_subpixel_positioning(true);
|
||||
cx.set_should_subpixel_position_fonts(true);
|
||||
cx.set_allows_font_subpixel_quantization(false);
|
||||
cx.set_should_subpixel_quantize_fonts(false);
|
||||
self.fonts[params.font_id.0]
|
||||
.native_font()
|
||||
.clone_with_font_size(f32::from(params.font_size) as CGFloat)
|
||||
.draw_glyphs(
|
||||
&[u32::from(params.glyph_id) as CGGlyph],
|
||||
&[CGPoint::new(
|
||||
(subpixel_shift.x / params.scale_factor) as CGFloat,
|
||||
(subpixel_shift.y / params.scale_factor) as CGFloat,
|
||||
)],
|
||||
cx,
|
||||
);
|
||||
|
||||
if params.is_emoji {
|
||||
// Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
|
||||
for pixel in bytes.chunks_exact_mut(4) {
|
||||
pixel.swap(0, 2);
|
||||
let a = pixel[3] as f32 / 255.;
|
||||
pixel[0] = (pixel[0] as f32 / a) as u8;
|
||||
pixel[1] = (pixel[1] as f32 / a) as u8;
|
||||
pixel[2] = (pixel[2] as f32 / a) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((bitmap_size, bytes))
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
|
||||
// Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
|
||||
let mut string = CFMutableAttributedString::new();
|
||||
{
|
||||
string.replace_str(&CFString::new(text), CFRange::init(0, 0));
|
||||
let utf16_line_len = string.char_len() as usize;
|
||||
|
||||
let mut ix_converter = StringIndexConverter::new(text);
|
||||
for run in font_runs {
|
||||
let utf8_end = ix_converter.utf8_ix + run.len;
|
||||
let utf16_start = ix_converter.utf16_ix;
|
||||
|
||||
if utf16_start >= utf16_line_len {
|
||||
break;
|
||||
}
|
||||
|
||||
ix_converter.advance_to_utf8_ix(utf8_end);
|
||||
let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);
|
||||
|
||||
let cf_range =
|
||||
CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
|
||||
|
||||
let font: &FontKitFont = &self.fonts[run.font_id.0];
|
||||
unsafe {
|
||||
string.set_attribute(
|
||||
cf_range,
|
||||
kCTFontAttributeName,
|
||||
&font.native_font().clone_with_font_size(font_size.into()),
|
||||
);
|
||||
}
|
||||
|
||||
if utf16_end == utf16_line_len {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
|
||||
let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
|
||||
|
||||
let mut runs = Vec::new();
|
||||
for run in line.glyph_runs().into_iter() {
|
||||
let attributes = run.attributes().unwrap();
|
||||
let font = unsafe {
|
||||
attributes
|
||||
.get(kCTFontAttributeName)
|
||||
.downcast::<CTFont>()
|
||||
.unwrap()
|
||||
};
|
||||
let font_id = self.id_for_native_font(font);
|
||||
|
||||
let mut ix_converter = StringIndexConverter::new(text);
|
||||
let mut glyphs = SmallVec::new();
|
||||
for ((glyph_id, position), glyph_utf16_ix) in run
|
||||
.glyphs()
|
||||
.iter()
|
||||
.zip(run.positions().iter())
|
||||
.zip(run.string_indices().iter())
|
||||
{
|
||||
let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
|
||||
ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
|
||||
glyphs.push(ShapedGlyph {
|
||||
id: (*glyph_id).into(),
|
||||
position: point(position.x as f32, position.y as f32).map(px),
|
||||
index: ix_converter.utf8_ix,
|
||||
is_emoji: self.is_emoji(font_id),
|
||||
});
|
||||
}
|
||||
|
||||
runs.push(ShapedRun { font_id, glyphs })
|
||||
}
|
||||
|
||||
let typographic_bounds = line.get_typographic_bounds();
|
||||
LineLayout {
|
||||
runs,
|
||||
font_size,
|
||||
width: typographic_bounds.width.into(),
|
||||
ascent: typographic_bounds.ascent.into(),
|
||||
descent: typographic_bounds.descent.into(),
|
||||
len: text.len(),
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_line(
|
||||
&self,
|
||||
text: &str,
|
||||
font_id: FontId,
|
||||
font_size: Pixels,
|
||||
width: Pixels,
|
||||
) -> Vec<usize> {
|
||||
let mut string = CFMutableAttributedString::new();
|
||||
string.replace_str(&CFString::new(text), CFRange::init(0, 0));
|
||||
let cf_range = CFRange::init(0, text.encode_utf16().count() as isize);
|
||||
let font = &self.fonts[font_id.0];
|
||||
unsafe {
|
||||
string.set_attribute(
|
||||
cf_range,
|
||||
kCTFontAttributeName,
|
||||
&font.native_font().clone_with_font_size(font_size.into()),
|
||||
);
|
||||
|
||||
let typesetter = CTTypesetterCreateWithAttributedString(string.as_concrete_TypeRef());
|
||||
let mut ix_converter = StringIndexConverter::new(text);
|
||||
let mut break_indices = Vec::new();
|
||||
while ix_converter.utf8_ix < text.len() {
|
||||
let utf16_len = CTTypesetterSuggestLineBreak(
|
||||
typesetter,
|
||||
ix_converter.utf16_ix as isize,
|
||||
width.into(),
|
||||
) as usize;
|
||||
ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len);
|
||||
if ix_converter.utf8_ix >= text.len() {
|
||||
break;
|
||||
}
|
||||
break_indices.push(ix_converter.utf8_ix);
|
||||
}
|
||||
break_indices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct StringIndexConverter<'a> {
|
||||
text: &'a str,
|
||||
utf8_ix: usize,
|
||||
utf16_ix: usize,
|
||||
}
|
||||
|
||||
impl<'a> StringIndexConverter<'a> {
|
||||
fn new(text: &'a str) -> Self {
|
||||
Self {
|
||||
text,
|
||||
utf8_ix: 0,
|
||||
utf16_ix: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_to_utf8_ix(&mut self, utf8_target: usize) {
|
||||
for (ix, c) in self.text[self.utf8_ix..].char_indices() {
|
||||
if self.utf8_ix + ix >= utf8_target {
|
||||
self.utf8_ix += ix;
|
||||
return;
|
||||
}
|
||||
self.utf16_ix += c.len_utf16();
|
||||
}
|
||||
self.utf8_ix = self.text.len();
|
||||
}
|
||||
|
||||
fn advance_to_utf16_ix(&mut self, utf16_target: usize) {
|
||||
for (ix, c) in self.text[self.utf8_ix..].char_indices() {
|
||||
if self.utf16_ix >= utf16_target {
|
||||
self.utf8_ix += ix;
|
||||
return;
|
||||
}
|
||||
self.utf16_ix += c.len_utf16();
|
||||
}
|
||||
self.utf8_ix = self.text.len();
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct __CFTypesetter(c_void);
|
||||
|
||||
pub type CTTypesetterRef = *const __CFTypesetter;
|
||||
|
||||
#[link(name = "CoreText", kind = "framework")]
|
||||
extern "C" {
|
||||
fn CTTypesetterCreateWithAttributedString(string: CFAttributedStringRef) -> CTTypesetterRef;
|
||||
|
||||
fn CTTypesetterSuggestLineBreak(
|
||||
typesetter: CTTypesetterRef,
|
||||
start_index: CFIndex,
|
||||
width: f64,
|
||||
) -> CFIndex;
|
||||
}
|
||||
|
||||
impl From<Metrics> for FontMetrics {
|
||||
fn from(metrics: Metrics) -> Self {
|
||||
FontMetrics {
|
||||
units_per_em: metrics.units_per_em,
|
||||
ascent: metrics.ascent,
|
||||
descent: metrics.descent,
|
||||
line_gap: metrics.line_gap,
|
||||
underline_position: metrics.underline_position,
|
||||
underline_thickness: metrics.underline_thickness,
|
||||
cap_height: metrics.cap_height,
|
||||
x_height: metrics.x_height,
|
||||
bounding_box: metrics.bounding_box.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RectF> for Bounds<f32> {
|
||||
fn from(rect: RectF) -> Self {
|
||||
Bounds {
|
||||
origin: point(rect.origin_x(), rect.origin_y()),
|
||||
size: size(rect.width(), rect.height()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RectI> for Bounds<DevicePixels> {
|
||||
fn from(rect: RectI) -> Self {
|
||||
Bounds {
|
||||
origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())),
|
||||
size: size(DevicePixels(rect.width()), DevicePixels(rect.height())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector2I> for Size<DevicePixels> {
|
||||
fn from(value: Vector2I) -> Self {
|
||||
size(value.x().into(), value.y().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RectI> for Bounds<i32> {
|
||||
fn from(rect: RectI) -> Self {
|
||||
Bounds {
|
||||
origin: point(rect.origin_x(), rect.origin_y()),
|
||||
size: size(rect.width(), rect.height()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point<u32>> for Vector2I {
|
||||
fn from(size: Point<u32>) -> Self {
|
||||
Vector2I::new(size.x as i32, size.y as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector2F> for Size<f32> {
|
||||
fn from(vec: Vector2F) -> Self {
|
||||
size(vec.x(), vec.y())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FontWeight> for FontkitWeight {
|
||||
fn from(value: FontWeight) -> Self {
|
||||
FontkitWeight(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FontStyle> for FontkitStyle {
|
||||
fn from(style: FontStyle) -> Self {
|
||||
match style {
|
||||
FontStyle::Normal => FontkitStyle::Normal,
|
||||
FontStyle::Italic => FontkitStyle::Italic,
|
||||
FontStyle::Oblique => FontkitStyle::Oblique,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use crate::AppContext;
|
||||
// use font_kit::properties::{Style, Weight};
|
||||
// use platform::FontSystem as _;
|
||||
|
||||
// #[crate::test(self, retries = 5)]
|
||||
// fn test_layout_str(_: &mut AppContext) {
|
||||
// // This is failing intermittently on CI and we don't have time to figure it out
|
||||
// let fonts = FontSystem::new();
|
||||
// let menlo = fonts.load_family("Menlo", &Default::default()).unwrap();
|
||||
// let menlo_regular = RunStyle {
|
||||
// font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
// let menlo_italic = RunStyle {
|
||||
// font_id: fonts
|
||||
// .select_font(&menlo, Properties::new().style(Style::Italic))
|
||||
// .unwrap(),
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
// let menlo_bold = RunStyle {
|
||||
// font_id: fonts
|
||||
// .select_font(&menlo, Properties::new().weight(Weight::BOLD))
|
||||
// .unwrap(),
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
// assert_ne!(menlo_regular, menlo_italic);
|
||||
// assert_ne!(menlo_regular, menlo_bold);
|
||||
// assert_ne!(menlo_italic, menlo_bold);
|
||||
|
||||
// let line = fonts.layout_line(
|
||||
// "hello world",
|
||||
// 16.0,
|
||||
// &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
|
||||
// );
|
||||
// assert_eq!(line.runs.len(), 3);
|
||||
// assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
|
||||
// assert_eq!(line.runs[0].glyphs.len(), 2);
|
||||
// assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
|
||||
// assert_eq!(line.runs[1].glyphs.len(), 4);
|
||||
// assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
|
||||
// assert_eq!(line.runs[2].glyphs.len(), 5);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_glyph_offsets() -> crate::Result<()> {
|
||||
// let fonts = FontSystem::new();
|
||||
// let zapfino = fonts.load_family("Zapfino", &Default::default())?;
|
||||
// let zapfino_regular = RunStyle {
|
||||
// font_id: fonts.select_font(&zapfino, &Properties::new())?,
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
// let menlo = fonts.load_family("Menlo", &Default::default())?;
|
||||
// let menlo_regular = RunStyle {
|
||||
// font_id: fonts.select_font(&menlo, &Properties::new())?,
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
|
||||
// let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
|
||||
// let line = fonts.layout_line(
|
||||
// text,
|
||||
// 16.0,
|
||||
// &[
|
||||
// (9, zapfino_regular),
|
||||
// (13, menlo_regular),
|
||||
// (text.len() - 22, zapfino_regular),
|
||||
// ],
|
||||
// );
|
||||
// assert_eq!(
|
||||
// line.runs
|
||||
// .iter()
|
||||
// .flat_map(|r| r.glyphs.iter())
|
||||
// .map(|g| g.index)
|
||||
// .collect::<Vec<_>>(),
|
||||
// vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37],
|
||||
// );
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// #[ignore]
|
||||
// fn test_rasterize_glyph() {
|
||||
// use std::{fs::File, io::BufWriter, path::Path};
|
||||
|
||||
// let fonts = FontSystem::new();
|
||||
// let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap();
|
||||
// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
|
||||
// let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
|
||||
|
||||
// const VARIANTS: usize = 1;
|
||||
// for i in 0..VARIANTS {
|
||||
// let variant = i as f32 / VARIANTS as f32;
|
||||
// let (bounds, bytes) = fonts
|
||||
// .rasterize_glyph(
|
||||
// font_id,
|
||||
// 16.0,
|
||||
// glyph_id,
|
||||
// vec2f(variant, variant),
|
||||
// 2.,
|
||||
// RasterizationOptions::Alpha,
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
|
||||
// let path = Path::new(&name);
|
||||
// let file = File::create(path).unwrap();
|
||||
// let w = &mut BufWriter::new(file);
|
||||
|
||||
// let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
|
||||
// encoder.set_color(png::ColorType::Grayscale);
|
||||
// encoder.set_depth(png::BitDepth::Eight);
|
||||
// let mut writer = encoder.write_header().unwrap();
|
||||
// writer.write_image_data(&bytes).unwrap();
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_wrap_line() {
|
||||
// let fonts = FontSystem::new();
|
||||
// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
|
||||
// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
|
||||
|
||||
// let line = "one two three four five\n";
|
||||
// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
|
||||
// assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]);
|
||||
|
||||
// let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n";
|
||||
// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
|
||||
// assert_eq!(
|
||||
// wrap_boundaries,
|
||||
// &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),]
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_layout_line_bom_char() {
|
||||
// let fonts = FontSystem::new();
|
||||
// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
|
||||
// let style = RunStyle {
|
||||
// font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
|
||||
// color: Default::default(),
|
||||
// underline: Default::default(),
|
||||
// };
|
||||
|
||||
// let line = "\u{feff}";
|
||||
// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
|
||||
// assert_eq!(layout.len, line.len());
|
||||
// assert!(layout.runs.is_empty());
|
||||
|
||||
// let line = "a\u{feff}b";
|
||||
// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
|
||||
// assert_eq!(layout.len, line.len());
|
||||
// assert_eq!(layout.runs.len(), 1);
|
||||
// assert_eq!(layout.runs[0].glyphs.len(), 2);
|
||||
// assert_eq!(layout.runs[0].glyphs[0].id, 68); // a
|
||||
// // There's no glyph for \u{feff}
|
||||
// assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
|
||||
// }
|
||||
// }
|
File diff suppressed because it is too large
Load diff
|
@ -1,14 +1,13 @@
|
|||
use std::ffi::CStr;
|
||||
|
||||
use crate::platform::Appearance;
|
||||
use crate::WindowAppearance;
|
||||
use cocoa::{
|
||||
appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight},
|
||||
base::id,
|
||||
foundation::NSString,
|
||||
};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use std::ffi::CStr;
|
||||
|
||||
impl Appearance {
|
||||
impl WindowAppearance {
|
||||
pub unsafe fn from_native(appearance: id) -> Self {
|
||||
let name: id = msg_send![appearance, name];
|
||||
if name == NSAppearanceNameVibrantLight {
|
Loading…
Add table
Add a link
Reference in a new issue