Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define a macro to create self-referencing type and use it for font face #184

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 39 additions & 92 deletions src/font/mod.rs
Original file line number Diff line number Diff line change
@@ -1,109 +1,54 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pub(crate) mod fallback;

use alloc::boxed::Box;
use core::fmt;

use alloc::sync::Arc;

pub use self::system::*;
mod system;

pub use font_inner::Font;
mod owned_face {
impl_self_ref!(OwnedFace, rustybuzz::Face<'static>, rustybuzz::Face<'this>);
}
use owned_face::*;
Comment on lines +11 to +14
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrapping this up in a module is necessary to prevent accessing the fields incorrectly so it should probably be part of the macro


/// Encapsulates the self-referencing `Font` struct to ensure all field accesses have to go through
/// safe methods.
mod font_inner {
use super::*;
use aliasable::boxed::AliasableBox;
use core::fmt;
pub struct Font {
#[cfg(feature = "swash")]
swash: (u32, swash::CacheKey),
rustybuzz: OwnedFace<Arc<dyn AsRef<[u8]> + Send + Sync>>,
data: Arc<dyn AsRef<[u8]> + Send + Sync>,
id: fontdb::ID,
}

/// A font
//
// # Safety invariant
//
// `data` must never have a mutable reference taken, nor be modified during the lifetime of
// this `Font`.
pub struct Font {
#[cfg(feature = "swash")]
swash: (u32, swash::CacheKey),
rustybuzz: rustybuzz::Face<'static>,
// Note: This field must be after rustybuzz to ensure that it is dropped later. Otherwise
// there would be a dangling reference when dropping rustybuzz.
data: aliasable::boxed::AliasableBox<Arc<dyn AsRef<[u8]> + Send + Sync>>,
id: fontdb::ID,
impl fmt::Debug for Font {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Font")
.field("id", &self.id)
.finish_non_exhaustive()
}
}

impl fmt::Debug for Font {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Font")
.field("id", &self.id)
.finish_non_exhaustive()
}
impl Font {
pub fn id(&self) -> fontdb::ID {
self.id
}

pub(super) struct FontTryBuilder<
RustybuzzBuilder: for<'this> FnOnce(
&'this Arc<dyn AsRef<[u8]> + Send + Sync>,
) -> Option<rustybuzz::Face<'this>>,
> {
pub(super) id: fontdb::ID,
pub(super) data: Arc<dyn AsRef<[u8]> + Send + Sync>,
pub(super) rustybuzz_builder: RustybuzzBuilder,
#[cfg(feature = "swash")]
pub(super) swash: (u32, swash::CacheKey),
pub fn data(&self) -> &[u8] {
(*self.data).as_ref()
}
impl<
RustybuzzBuilder: for<'this> FnOnce(
&'this Arc<dyn AsRef<[u8]> + Send + Sync>,
) -> Option<rustybuzz::Face<'this>>,
> FontTryBuilder<RustybuzzBuilder>
{
pub(super) fn try_build(self) -> Option<Font> {
unsafe fn change_lifetime<'old, 'new: 'old, T: 'new>(data: &'old T) -> &'new T {
&*(data as *const _)
}

let data: AliasableBox<Arc<dyn AsRef<[u8]> + Send + Sync>> =
AliasableBox::from_unique(Box::new(self.data));

// Safety: We use AliasableBox to allow the references in rustybuzz::Face to alias with
// the data stored behind the AliasableBox. In addition the entire public interface of
// Font ensures that no mutable reference is given to data. And finally we use
// for<'this> for the rustybuzz_builder to ensure it can't leak a reference. Combined
// this ensures that it is sound to produce a self-referential type.
let rustybuzz = (self.rustybuzz_builder)(unsafe { change_lifetime(&*data) })?;

Some(Font {
id: self.id,
data,
rustybuzz,
#[cfg(feature = "swash")]
swash: self.swash,
})
}
pub fn rustybuzz(&self) -> &rustybuzz::Face<'_> {
self.rustybuzz.as_ref()
}

impl Font {
pub fn id(&self) -> fontdb::ID {
self.id
}

pub fn data(&self) -> &[u8] {
// Safety: This only gives an immutable access to `data`.
(**self.data).as_ref()
}

pub fn rustybuzz(&self) -> &rustybuzz::Face<'_> {
&self.rustybuzz
}

#[cfg(feature = "swash")]
pub fn as_swash(&self) -> swash::FontRef<'_> {
let swash = &self.swash;
swash::FontRef {
data: self.data(),
offset: swash.0,
key: swash.1,
}
#[cfg(feature = "swash")]
pub fn as_swash(&self) -> swash::FontRef<'_> {
let swash = &self.swash;
swash::FontRef {
data: self.data(),
offset: swash.0,
key: swash.1,
}
}
}
Expand All @@ -121,16 +66,18 @@ impl Font {
#[cfg(feature = "std")]
fontdb::Source::SharedFile(_path, data) => Arc::clone(data),
};
font_inner::FontTryBuilder {

Some(Self {
id: info.id,
#[cfg(feature = "swash")]
swash: {
let swash = swash::FontRef::from_index((*data).as_ref(), info.index as usize)?;
(swash.offset, swash.key)
},
rustybuzz: OwnedFace::new(data.clone(), |data| {
rustybuzz::Face::from_slice(data.as_ref().as_ref(), info.index)
})?,
data,
rustybuzz_builder: |data| rustybuzz::Face::from_slice((**data).as_ref(), info.index),
}
.try_build()
})
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ extern crate alloc;
#[cfg(not(any(feature = "std", feature = "no_std")))]
compile_error!("Either the `std` or `no_std` feature must be enabled");

#[macro_use]
mod macros;

pub use self::attrs::*;
mod attrs;

Expand Down
69 changes: 69 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/// Used for creating container type that self-reference owned data,
/// see also <https://morestina.net/blog/1868/self-referential-types-for-fun-and-profit>
///
/// # Example
///
/// ```ignore
/// struct Data();
/// struct SomeRef<'a>(&'a Data);
///
/// mod inner {
/// impl_self_ref!(Container, SomeRef<'static>, SomeRef<'this>);
/// }
/// use inner::Container;
///
/// let container = Container::new(Data(), |data| SomeRef(data));
/// let some_ref = container.as_ref();
/// ```
macro_rules! impl_self_ref {
($SelfRef:ident, $RefStatic:ty, $RefThis:ty) => {
/// # Safety invariant
///
/// `data` must never have a mutable reference taken, nor be modified during the lifetime of
/// this struct
pub struct $SelfRef<T> {
/// `data_ref` could self-referencing `data`
data_ref: $RefStatic,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field needs to be wrapped in maybe_dangling::MaybeDangling or core::mem::MaybeUninit to prevent the compiler from potentially assuming any references it contains will be valid till the end of a function when $SelfRef<T> is passed as a function parameter (see the issue I linked above). Any such references will only be valid until this value is dropped.

/// `data` field must be after `data_ref` to ensure that it is dropped later
data: ::aliasable::boxed::AliasableBox<T>,
}
impl<T> ::core::fmt::Debug for $SelfRef<T> {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
f.debug_struct(stringify!($SelfRef)).finish_non_exhaustive()
}
}

impl<'this, T> AsRef<$RefThis> for $SelfRef<T>
where
Self: 'this,
{
fn as_ref(&self) -> &$RefThis {
&self.data_ref
}
}

impl<T> $SelfRef<T> {
pub fn new<F>(data: T, builder: F) -> Option<Self>
where
T: 'static,
for<'this> F: FnOnce(&'this T) -> Option<$RefThis>,
{
unsafe fn change_lifetime<'old, 'new: 'old, T: 'new>(data: &'old T) -> &'new T {
&*(data as *const _)
}
let data =
::aliasable::boxed::AliasableBox::from_unique(::alloc::boxed::Box::new(data));
let data_ref = builder(unsafe { change_lifetime(&data) })?;
Some(Self { data_ref, data })
}

/// # Safety
///
/// Allows immutable access to data only
#[allow(dead_code)]
pub fn as_backing_data(&self) -> &T {
&self.data
}
}
};
}